Compare commits

...

10 commits

Author SHA1 Message Date
d00b98edb9 add todos 2025-05-25 13:50:25 +02:00
8bf37d0d1a remove unwraps 2025-05-25 13:48:20 +02:00
0da69454c7 cleanup 2025-05-25 13:38:43 +02:00
822cf12d59 cleanup 2025-05-25 12:13:07 +02:00
a888888c9d cleanup 2025-05-25 12:11:12 +02:00
6f265c3ec5 add docs 2025-05-25 08:07:30 +02:00
b483dcf503 add documentation 2025-05-25 08:06:38 +02:00
06a7f51805 fix 2025-05-25 07:59:13 +02:00
04d2b6b93b rename 2025-05-25 07:56:35 +02:00
9e3dce14a2 bump rust edition 2025-05-24 22:51:07 +02:00
11 changed files with 110 additions and 85 deletions

View file

@ -2,7 +2,7 @@
name = "bevy_scriptum" name = "bevy_scriptum"
authors = ["Jaroslaw Konik <konikjar@gmail.com>"] authors = ["Jaroslaw Konik <konikjar@gmail.com>"]
version = "0.8.1" version = "0.8.1"
edition = "2021" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
categories = ["game-development"] categories = ["game-development"]
@ -46,7 +46,7 @@ path = "examples/rhai/current_entity.rs"
required-features = ["rhai"] required-features = ["rhai"]
[[example]] [[example]]
name = "custom_type_rhai" name = "custom5type_rhai"
path = "examples/rhai/custom_type.rs" path = "examples/rhai/custom_type.rs"
required-features = ["rhai"] required-features = ["rhai"]

View file

@ -2,5 +2,3 @@ fun_with_string_param("hello")
fun_with_i64_param(5) fun_with_i64_param(5)
fun_with_multiple_params(5, "hello") fun_with_multiple_params(5, "hello")
fun_with_i64_and_array_param(5, [1, 2, "third element"]) fun_with_i64_and_array_param(5, [1, 2, "third element"])
# TODO: add test for heteregenous array
# TODO: for every runtime add example for wrapping with BevyEntity and BevyVec

View file

@ -1,10 +1,10 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_scriptum::ScriptingError;
use bevy_scriptum::prelude::*; use bevy_scriptum::prelude::*;
use bevy_scriptum::runtimes::ruby::magnus; use bevy_scriptum::runtimes::ruby::magnus;
use bevy_scriptum::runtimes::ruby::magnus::Module as _; use bevy_scriptum::runtimes::ruby::magnus::Module as _;
use bevy_scriptum::runtimes::ruby::magnus::Object as _; use bevy_scriptum::runtimes::ruby::magnus::Object as _;
use bevy_scriptum::runtimes::ruby::prelude::*; use bevy_scriptum::runtimes::ruby::prelude::*;
use bevy_scriptum::ScriptingError;
fn main() { fn main() {
App::new() App::new()
@ -39,7 +39,7 @@ fn startup(
assets_server: Res<AssetServer>, assets_server: Res<AssetServer>,
) { ) {
scripting_runtime scripting_runtime
.with_engine_thread(|ruby| { .with_engine_send(|ruby| {
let my_type = ruby.define_class("MyType", ruby.class_object())?; let my_type = ruby.define_class("MyType", ruby.class_object())?;
my_type.define_singleton_method("new", magnus::function!(MyType::new, 0))?; my_type.define_singleton_method("new", magnus::function!(MyType::new, 0))?;
my_type.define_method("my_method", magnus::method!(MyType::my_method, 0))?; my_type.define_method("my_method", magnus::method!(MyType::my_method, 0))?;

View file

@ -1,6 +1,6 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_scriptum::prelude::*; use bevy_scriptum::prelude::*;
use bevy_scriptum::runtimes::ruby::{prelude::*, RArray}; use bevy_scriptum::runtimes::ruby::{RArray, prelude::*};
fn main() { fn main() {
App::new() App::new()
@ -31,7 +31,7 @@ fn main() {
.add_function( .add_function(
String::from("fun_with_i64_and_array_param"), String::from("fun_with_i64_and_array_param"),
|In((x, y)): In<(i64, RArray)>, runtime: Res<RubyRuntime>| { |In((x, y)): In<(i64, RArray)>, runtime: Res<RubyRuntime>| {
runtime.with_engine_thread(move |ruby| { runtime.with_engine_send(move |ruby| {
println!( println!(
"called with i64: {} and dynamically typed array: {:?}", "called with i64: {} and dynamically typed array: {:?}",
x, x,

View file

@ -1,8 +1,8 @@
use bevy::{app::AppExit, prelude::*}; use bevy::{app::AppExit, prelude::*};
use bevy_scriptum::prelude::*; use bevy_scriptum::prelude::*;
use bevy_scriptum::runtimes::ruby::prelude::*; use bevy_scriptum::runtimes::ruby::prelude::*;
use magnus::value::InnerValue;
use magnus::TryConvert; use magnus::TryConvert;
use magnus::value::InnerValue;
fn main() { fn main() {
App::new() App::new()
@ -33,7 +33,7 @@ fn call_lua_on_update_from_rust(
.call_fn("get_value", &mut script_data, entity, ()) .call_fn("get_value", &mut script_data, entity, ())
.unwrap() .unwrap()
.0; .0;
scripting_runtime.with_engine_thread(move |ruby| { scripting_runtime.with_engine(|ruby| {
let val: i32 = TryConvert::try_convert(val.get_inner_with(&ruby)).unwrap(); let val: i32 = TryConvert::try_convert(val.get_inner_with(&ruby)).unwrap();
println!("script returned: {}", val); println!("script returned: {}", val);
}); });

View file

@ -2,7 +2,7 @@ use bevy::prelude::*;
use core::any::TypeId; use core::any::TypeId;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::{promise::Promise, Runtime}; use crate::{Runtime, promise::Promise};
/// A system that can be used to call a script function. /// A system that can be used to call a script function.
pub struct CallbackSystem<R: Runtime> { pub struct CallbackSystem<R: Runtime> {
@ -79,7 +79,7 @@ where
let mut runtime = world.get_resource_mut::<R>().expect("No runtime resource"); let mut runtime = world.get_resource_mut::<R>().expect("No runtime resource");
if R::needs_own_thread() { if R::needs_own_thread() {
runtime.with_engine_thread_mut(move |engine| { runtime.with_engine_send_mut(move |engine| {
Out::into_runtime_value_with_engine(result, engine) Out::into_runtime_value_with_engine(result, engine)
}) })
} else { } else {
@ -111,7 +111,7 @@ macro_rules! impl_tuple {
let system_fn = move |args: In<Vec<RN::Value>>, world: &mut World| { let system_fn = move |args: In<Vec<RN::Value>>, world: &mut World| {
let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource"); let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource");
let args = if RN::needs_own_thread() { let args = if RN::needs_own_thread() {
runtime.with_engine_thread_mut(move |engine| { runtime.with_engine_send_mut(move |engine| {
( (
$($t::from_runtime_value_with_engine(args.get($idx).expect(&format!("Failed to get function argument for index {}", $idx)).clone(), engine), )+ $($t::from_runtime_value_with_engine(args.get($idx).expect(&format!("Failed to get function argument for index {}", $idx)).clone(), engine), )+
) )
@ -128,7 +128,7 @@ macro_rules! impl_tuple {
inner_system.apply_deferred(world); inner_system.apply_deferred(world);
let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource"); let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource");
if RN::needs_own_thread() { if RN::needs_own_thread() {
runtime.with_engine_thread_mut(move |engine| { runtime.with_engine_send_mut(move |engine| {
Out::into_runtime_value_with_engine(result, engine) Out::into_runtime_value_with_engine(result, engine)
}) })
} else { } else {

View file

@ -305,7 +305,11 @@ pub trait Runtime: Resource + Default {
/// Provides mutable reference to raw scripting engine instance. /// Provides mutable reference to raw scripting engine instance.
/// Can be used to directly interact with an interpreter to use interfaces /// Can be used to directly interact with an interpreter to use interfaces
/// that bevy_scriptum does not provided adapters for. /// that bevy_scriptum does not provided adapters for.
fn with_engine_thread_mut<T: Send + 'static>( /// Using this function make the closure be executed on another thread for
/// some runtimes. If you need to operate on non-`'static` borrows and/or
/// `!Send` data, you can use `with_engine_mut` - it may not be implemented
/// for some of the runtimes though.
fn with_engine_send_mut<T: Send + 'static>(
&mut self, &mut self,
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
) -> T; ) -> T;
@ -313,7 +317,11 @@ pub trait Runtime: Resource + Default {
/// Provides immutable reference to raw scripting engine instance. /// Provides immutable reference to raw scripting engine instance.
/// Can be used to directly interact with an interpreter to use interfaces /// Can be used to directly interact with an interpreter to use interfaces
/// that bevy_scriptum does not provided adapters for. /// that bevy_scriptum does not provided adapters for.
fn with_engine_thread<T: Send + 'static>( /// Using this function make the closure be executed on another thread for
/// some runtimes. If you need to operate on non-`'static` borrows and/or
/// `!Send` data, you can use `with_engine` - it may not be implemented
/// for some of the runtimes though.
fn with_engine_send<T: Send + 'static>(
&self, &self,
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
) -> T; ) -> T;
@ -321,11 +329,15 @@ pub trait Runtime: Resource + Default {
/// Provides mutable reference to raw scripting engine instance. /// Provides mutable reference to raw scripting engine instance.
/// Can be used to directly interact with an interpreter to use interfaces /// Can be used to directly interact with an interpreter to use interfaces
/// that bevy_scriptum does not provided adapters for. /// that bevy_scriptum does not provided adapters for.
/// May not be implemented for runtimes which require the closure to pass
/// thread boundary - use `with_engine_send_mut` then.
fn with_engine_mut<T>(&mut self, f: impl FnOnce(&mut Self::RawEngine) -> T) -> T; fn with_engine_mut<T>(&mut self, f: impl FnOnce(&mut Self::RawEngine) -> T) -> T;
/// Provides immutable reference to raw scripting engine instance. /// Provides immutable reference to raw scripting engine instance.
/// Can be used to directly interact with an interpreter to use interfaces /// Can be used to directly interact with an interpreter to use interfaces
/// that bevy_scriptum does not provided adapters for. /// that bevy_scriptum does not provided adapters for.
/// May not be implemented for runtimes which require the closure to pass
/// thread boundary - use `with_engine_send` then.
fn with_engine<T>(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T; fn with_engine<T>(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T;
fn eval( fn eval(
@ -342,12 +354,12 @@ pub trait Runtime: Resource + Default {
name: String, name: String,
arg_types: Vec<TypeId>, arg_types: Vec<TypeId>,
f: impl Fn( f: impl Fn(
Self::CallContext, Self::CallContext,
Vec<Self::Value>, Vec<Self::Value>,
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError> ) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,
) -> Result<(), ScriptingError>; ) -> Result<(), ScriptingError>;
/// Calls a function by name defined within the runtime in the context of the /// Calls a function by name defined within the runtime in the context of the

View file

@ -1,7 +1,6 @@
use bevy::{ use bevy::{
asset::Asset, asset::Asset,
ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel}, ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel},
log,
math::Vec3, math::Vec3,
reflect::TypePath, reflect::TypePath,
}; };
@ -13,10 +12,10 @@ use serde::Deserialize;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::{ use crate::{
ENTITY_VAR_NAME, FuncArgs, Runtime, ScriptingError,
assets::GetExtensions, assets::GetExtensions,
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine}, callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
promise::Promise, promise::Promise,
FuncArgs, Runtime, ScriptingError, ENTITY_VAR_NAME,
}; };
type LuaEngine = Arc<Mutex<Lua>>; type LuaEngine = Arc<Mutex<Lua>>;
@ -197,14 +196,14 @@ impl Runtime for LuaRuntime {
name: String, name: String,
_arg_types: Vec<std::any::TypeId>, _arg_types: Vec<std::any::TypeId>,
f: impl Fn( f: impl Fn(
Self::CallContext, Self::CallContext,
Vec<Self::Value>, Vec<Self::Value>,
) -> Result< ) -> Result<
crate::promise::Promise<Self::CallContext, Self::Value>, crate::promise::Promise<Self::CallContext, Self::Value>,
crate::ScriptingError, crate::ScriptingError,
> + Send > + Send
+ Sync + Sync
+ 'static, + 'static,
) -> Result<(), crate::ScriptingError> { ) -> Result<(), crate::ScriptingError> {
self.with_engine(|engine| { self.with_engine(|engine| {
let func = engine let func = engine
@ -283,14 +282,14 @@ impl Runtime for LuaRuntime {
f(&engine) f(&engine)
} }
fn with_engine_thread_mut<T: Send + 'static>( fn with_engine_send_mut<T: Send + 'static>(
&mut self, &mut self,
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
) -> T { ) -> T {
self.with_engine_mut(f) self.with_engine_mut(f)
} }
fn with_engine_thread<T: Send + 'static>( fn with_engine_send<T: Send + 'static>(
&self, &self,
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
) -> T { ) -> T {

View file

@ -3,7 +3,6 @@ use std::fmt::Debug;
use bevy::{ use bevy::{
asset::Asset, asset::Asset,
ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel}, ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel},
log,
math::Vec3, math::Vec3,
reflect::TypePath, reflect::TypePath,
}; };
@ -11,10 +10,10 @@ use rhai::{CallFnOptions, Dynamic, Engine, FnPtr, Scope, Variant};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
ENTITY_VAR_NAME, FuncArgs, Runtime, ScriptingError,
assets::GetExtensions, assets::GetExtensions,
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine}, callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
promise::Promise, promise::Promise,
FuncArgs, Runtime, ScriptingError, ENTITY_VAR_NAME,
}; };
#[derive(Asset, Debug, Deserialize, TypePath)] #[derive(Asset, Debug, Deserialize, TypePath)]
@ -118,12 +117,12 @@ impl Runtime for RhaiRuntime {
name: String, name: String,
arg_types: Vec<std::any::TypeId>, arg_types: Vec<std::any::TypeId>,
f: impl Fn( f: impl Fn(
Self::CallContext, Self::CallContext,
Vec<Self::Value>, Vec<Self::Value>,
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError> ) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,
) -> Result<(), ScriptingError> { ) -> Result<(), ScriptingError> {
self.engine self.engine
.register_raw_fn(name, arg_types, move |context, args| { .register_raw_fn(name, arg_types, move |context, args| {
@ -192,14 +191,14 @@ impl Runtime for RhaiRuntime {
f(&self.engine) f(&self.engine)
} }
fn with_engine_thread_mut<T: Send + 'static>( fn with_engine_send_mut<T: Send + 'static>(
&mut self, &mut self,
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
) -> T { ) -> T {
self.with_engine_mut(f) self.with_engine_mut(f)
} }
fn with_engine_thread<T: Send + 'static>( fn with_engine_send<T: Send + 'static>(
&self, &self,
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
) -> T { ) -> T {

View file

@ -14,20 +14,20 @@ use bevy::{
tasks::futures_lite::io, tasks::futures_lite::io,
}; };
use magnus::{ use magnus::{
DataType, DataTypeFunctions, IntoValue, Object, RClass, RModule, Ruby, TryConvert, TypedData,
block::Proc, block::Proc,
data_type_builder, function, data_type_builder, function,
value::{Lazy, ReprValue}, value::{Lazy, ReprValue},
DataType, DataTypeFunctions, IntoValue, Object, RClass, RModule, Ruby, TryConvert, TypedData,
}; };
use magnus::{method, prelude::*}; use magnus::{method, prelude::*};
use rb_sys::{ruby_init_stack, VALUE}; use rb_sys::{VALUE, ruby_init_stack};
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::{
FuncArgs, Runtime, ScriptingError,
assets::GetExtensions, assets::GetExtensions,
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine}, callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
promise::Promise, promise::Promise,
FuncArgs, Runtime, ScriptingError,
}; };
#[derive(Resource)] #[derive(Resource)]
@ -67,21 +67,26 @@ static RUBY_THREAD: LazyLock<Arc<(Mutex<Option<RubyThread>>, Condvar)>> =
LazyLock::new(|| Arc::new((Mutex::new(Some(RubyThread::spawn())), Condvar::new()))); LazyLock::new(|| Arc::new((Mutex::new(Some(RubyThread::spawn())), Condvar::new())));
impl RubyThread { impl RubyThread {
fn build_ruby_process_argv() -> anyhow::Result<Vec<*mut i8>> {
Ok(vec![
CString::new("ruby")?.into_raw(),
CString::new("-e")?.into_raw(),
CString::new("")?.into_raw(),
])
}
fn spawn() -> Self { fn spawn() -> Self {
let (sender, receiver) = crossbeam_channel::unbounded::<Box<dyn FnOnce(Ruby) + Send>>(); let (sender, receiver) = crossbeam_channel::unbounded::<Box<dyn FnOnce(Ruby) + Send>>();
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
let mut argv = vec![
CString::new("ruby").unwrap().into_raw(),
CString::new("-e").unwrap().into_raw(),
CString::new("").unwrap().into_raw(),
];
unsafe { unsafe {
let mut variable_in_this_stack_frame: VALUE = 0; let mut variable_in_this_stack_frame: VALUE = 0;
ruby_init_stack(&mut variable_in_this_stack_frame as *mut VALUE as *mut _); ruby_init_stack(&mut variable_in_this_stack_frame as *mut VALUE as *mut _);
rb_sys::ruby_init(); rb_sys::ruby_init();
rb_sys::ruby_init_loadpath();
let mut argv =
Self::build_ruby_process_argv().expect("Failed to build ruby process args");
rb_sys::ruby_options(argv.len() as i32, argv.as_mut_ptr()); rb_sys::ruby_options(argv.len() as i32, argv.as_mut_ptr());
}; };
while let Ok(f) = receiver.recv() { while let Ok(f) = receiver.recv() {
@ -128,7 +133,7 @@ unsafe impl TypedData for Promise<(), RubyValue> {
static CLASS: Lazy<RClass> = Lazy::new(|ruby| { static CLASS: Lazy<RClass> = Lazy::new(|ruby| {
let class = ruby let class = ruby
.define_module("Bevy") .define_module("Bevy")
.unwrap() .expect("Failed to define Bevy module")
.define_class("Promise", ruby.class_object()) .define_class("Promise", ruby.class_object())
.expect("Failed to define Bevy::Promise class in Ruby"); .expect("Failed to define Bevy::Promise class in Ruby");
class.undef_default_alloc_func(); class.undef_default_alloc_func();
@ -259,7 +264,7 @@ impl Default for RubyRuntime {
vec3.define_method("z", method!(BevyVec3::z, 0))?; vec3.define_method("z", method!(BevyVec3::z, 0))?;
Ok::<(), ScriptingError>(()) Ok::<(), ScriptingError>(())
})) }))
.unwrap(); .expect("Failed to define builtin types");
Self { Self {
ruby_thread: Some(ruby_thread), ruby_thread: Some(ruby_thread),
} }
@ -269,7 +274,9 @@ impl Default for RubyRuntime {
impl Drop for RubyRuntime { impl Drop for RubyRuntime {
fn drop(&mut self) { fn drop(&mut self) {
let (lock, cvar) = &*Arc::clone(&RUBY_THREAD); let (lock, cvar) = &*Arc::clone(&RUBY_THREAD);
let mut ruby_thread = lock.lock().unwrap(); let mut ruby_thread = lock
.lock()
.expect("Failed to lock ruby thread while dropping the runtime");
*ruby_thread = self.ruby_thread.take(); *ruby_thread = self.ruby_thread.take();
cvar.notify_all(); cvar.notify_all();
} }
@ -295,7 +302,7 @@ impl RubyRuntime {
) -> T { ) -> T {
self.ruby_thread self.ruby_thread
.as_ref() .as_ref()
.unwrap() .expect("No Ruby thread")
.execute(Box::new(move |ruby| f(&ruby))) .execute(Box::new(move |ruby| f(&ruby)))
} }
@ -305,7 +312,7 @@ impl RubyRuntime {
) -> T { ) -> T {
self.ruby_thread self.ruby_thread
.as_ref() .as_ref()
.unwrap() .expect("No Ruby thread")
.execute(Box::new(move |mut ruby| f(&mut ruby))) .execute(Box::new(move |mut ruby| f(&mut ruby)))
} }
} }
@ -323,16 +330,14 @@ impl Runtime for RubyRuntime {
type RawEngine = magnus::Ruby; type RawEngine = magnus::Ruby;
// TODO: it should be somehow possible to remove 'static here fn with_engine_send_mut<T: Send + 'static>(
fn with_engine_thread_mut<T: Send + 'static>(
&mut self, &mut self,
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
) -> T { ) -> T {
self.execute_in_thread_mut(f) self.execute_in_thread_mut(f)
} }
// TODO: it should be somehow possible to remove 'static here fn with_engine_send<T: Send + 'static>(
fn with_engine_thread<T: Send + 'static>(
&self, &self,
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static, f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
) -> T { ) -> T {
@ -340,11 +345,15 @@ impl Runtime for RubyRuntime {
} }
fn with_engine_mut<T>(&mut self, _f: impl FnOnce(&mut Self::RawEngine) -> T) -> T { fn with_engine_mut<T>(&mut self, _f: impl FnOnce(&mut Self::RawEngine) -> T) -> T {
unimplemented!("Ruby requires single threaded execution, use `with_engine_thread`"); unimplemented!(
"Ruby runtime requires sending execution to another thread, use `with_engine_mut_send`"
);
} }
fn with_engine<T>(&self, _f: impl FnOnce(&Self::RawEngine) -> T) -> T { fn with_engine<T>(&self, _f: impl FnOnce(&Self::RawEngine) -> T) -> T {
unimplemented!("Ruby requires single threaded execution, use `with_engine_thread`"); unimplemented!(
"Ruby runtime requires sending execution to another thread, use `with_engine_send`"
);
} }
fn eval( fn eval(
@ -354,15 +363,19 @@ impl Runtime for RubyRuntime {
) -> Result<Self::ScriptData, crate::ScriptingError> { ) -> Result<Self::ScriptData, crate::ScriptingError> {
let script = script.0.clone(); let script = script.0.clone();
self.execute_in_thread(Box::new(move |ruby: &Ruby| { self.execute_in_thread(Box::new(move |ruby: &Ruby| {
// TODO: refactor
let var = ruby let var = ruby
.class_object() .class_object()
.const_get::<_, RModule>("Bevy") .const_get::<_, RModule>("Bevy")
.unwrap() .expect("Failed to get Bevy module")
.const_get::<_, RClass>("Entity") .const_get::<_, RClass>("Entity")
.unwrap(); .expect("Failed to get Entity class");
var.ivar_set("_current", BevyEntity(entity)).unwrap();
var.ivar_set("_current", BevyEntity(entity))
.expect("Failed to set current entity handle");
let value = ruby.eval::<magnus::value::Value>(&script).unwrap(); let value = ruby.eval::<magnus::value::Value>(&script).unwrap();
var.ivar_set("_current", ruby.qnil().as_value()).unwrap(); var.ivar_set("_current", ruby.qnil().as_value())
.expect("Failed to unset current entity handle");
RubyValue::new(value) RubyValue::new(value)
})); }));
@ -374,14 +387,14 @@ impl Runtime for RubyRuntime {
name: String, name: String,
_arg_types: Vec<std::any::TypeId>, _arg_types: Vec<std::any::TypeId>,
f: impl Fn( f: impl Fn(
Self::CallContext, Self::CallContext,
Vec<Self::Value>, Vec<Self::Value>,
) -> Result< ) -> Result<
crate::promise::Promise<Self::CallContext, Self::Value>, crate::promise::Promise<Self::CallContext, Self::Value>,
crate::ScriptingError, crate::ScriptingError,
> + Send > + Send
+ Sync + Sync
+ 'static, + 'static,
) -> Result<(), crate::ScriptingError> { ) -> Result<(), crate::ScriptingError> {
type CallbackClosure = Box< type CallbackClosure = Box<
dyn Fn( dyn Fn(
@ -393,11 +406,14 @@ impl Runtime for RubyRuntime {
>; >;
static RUBY_CALLBACKS: LazyLock<Mutex<HashMap<String, CallbackClosure>>> = static RUBY_CALLBACKS: LazyLock<Mutex<HashMap<String, CallbackClosure>>> =
LazyLock::new(|| Mutex::new(HashMap::new())); LazyLock::new(|| Mutex::new(HashMap::new()));
let mut callbacks = RUBY_CALLBACKS.lock().unwrap(); let mut callbacks = RUBY_CALLBACKS
.lock()
.expect("Failed to lock callbacks static when registering a callback");
callbacks.insert(name.clone(), Box::new(f)); callbacks.insert(name.clone(), Box::new(f));
fn callback(args: &[magnus::Value]) -> magnus::Value { fn callback(args: &[magnus::Value]) -> magnus::Value {
let ruby = magnus::Ruby::get().unwrap(); let ruby = magnus::Ruby::get()
.expect("Failed to get a handle to Ruby API while processing callback");
let method_name: magnus::value::StaticSymbol = let method_name: magnus::value::StaticSymbol =
ruby.class_object().funcall("__method__", ()).unwrap(); ruby.class_object().funcall("__method__", ()).unwrap();
let method_name = method_name.name().unwrap(); let method_name = method_name.name().unwrap();
@ -430,6 +446,7 @@ impl Runtime for RubyRuntime {
) -> Result<Self::Value, crate::ScriptingError> { ) -> Result<Self::Value, crate::ScriptingError> {
let name = name.to_string(); let name = name.to_string();
self.execute_in_thread(Box::new(move |ruby: &Ruby| { self.execute_in_thread(Box::new(move |ruby: &Ruby| {
// TOOD: refactor
let var = ruby let var = ruby
.class_object() .class_object()
.const_get::<_, RModule>("Bevy") .const_get::<_, RModule>("Bevy")

View file

@ -6,7 +6,7 @@ use bevy::ecs::system::RunSystemOnce as _;
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))] #[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
use bevy::prelude::*; use bevy::prelude::*;
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))] #[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
use bevy_scriptum::{prelude::*, FuncArgs, Runtime}; use bevy_scriptum::{FuncArgs, Runtime, prelude::*};
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))] #[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
static TRACING_SUBSCRIBER: OnceLock<()> = OnceLock::new(); static TRACING_SUBSCRIBER: OnceLock<()> = OnceLock::new();
@ -621,7 +621,7 @@ mod lua_tests {
#[cfg(feature = "ruby")] #[cfg(feature = "ruby")]
mod ruby_tests { mod ruby_tests {
use bevy::prelude::*; use bevy::prelude::*;
use bevy_scriptum::runtimes::ruby::{prelude::*, RubyScriptData}; use bevy_scriptum::runtimes::ruby::{RubyScriptData, prelude::*};
use magnus::value::ReprValue; use magnus::value::ReprValue;
impl AssertStateKeyValue for RubyRuntime { impl AssertStateKeyValue for RubyRuntime {
@ -630,7 +630,7 @@ mod ruby_tests {
fn assert_state_key_value_i64(world: &World, _entity_id: Entity, key: &str, value: i64) { fn assert_state_key_value_i64(world: &World, _entity_id: Entity, key: &str, value: i64) {
let runtime = world.get_resource::<RubyRuntime>().unwrap(); let runtime = world.get_resource::<RubyRuntime>().unwrap();
let key = key.to_string(); let key = key.to_string();
runtime.with_engine_thread(move |engine| { runtime.with_engine_send(move |engine| {
let state: magnus::value::Value = engine.eval("$state").unwrap(); let state: magnus::value::Value = engine.eval("$state").unwrap();
let res: i64 = state.funcall_public("[]", (key,)).unwrap(); let res: i64 = state.funcall_public("[]", (key,)).unwrap();
assert_eq!(res, value) assert_eq!(res, value)
@ -640,7 +640,7 @@ mod ruby_tests {
fn assert_state_key_value_i32(world: &World, _entity_id: Entity, key: &str, value: i32) { fn assert_state_key_value_i32(world: &World, _entity_id: Entity, key: &str, value: i32) {
let runtime = world.get_resource::<RubyRuntime>().unwrap(); let runtime = world.get_resource::<RubyRuntime>().unwrap();
let key = key.to_string(); let key = key.to_string();
runtime.with_engine_thread(move |engine| { runtime.with_engine_send(move |engine| {
let state: magnus::value::Value = engine.eval("$state").unwrap(); let state: magnus::value::Value = engine.eval("$state").unwrap();
let res: i32 = state.funcall_public("[]", (key,)).unwrap(); let res: i32 = state.funcall_public("[]", (key,)).unwrap();
assert_eq!(res, value) assert_eq!(res, value)
@ -656,7 +656,7 @@ mod ruby_tests {
let runtime = world.get_resource::<RubyRuntime>().unwrap(); let runtime = world.get_resource::<RubyRuntime>().unwrap();
let key = key.to_string(); let key = key.to_string();
let value = value.to_string(); let value = value.to_string();
runtime.with_engine_thread(move |engine| { runtime.with_engine_send(move |engine| {
let state: magnus::value::Value = engine.eval("$state").unwrap(); let state: magnus::value::Value = engine.eval("$state").unwrap();
let res: String = state.funcall_public("[]", (key,)).unwrap(); let res: String = state.funcall_public("[]", (key,)).unwrap();
assert_eq!(res, value); assert_eq!(res, value);
@ -670,7 +670,7 @@ mod ruby_tests {
app.add_scripting::<RubyRuntime>(|_| {}); app.add_scripting::<RubyRuntime>(|_| {});
let runtime = app.world().get_resource::<RubyRuntime>().unwrap(); let runtime = app.world().get_resource::<RubyRuntime>().unwrap();
runtime.with_engine_thread(|engine| { runtime.with_engine_send(|engine| {
let symbol_string: String = engine.eval(":test_symbol.inspect").unwrap(); let symbol_string: String = engine.eval(":test_symbol.inspect").unwrap();
assert_eq!(symbol_string, ":test_symbol") assert_eq!(symbol_string, ":test_symbol")
}); });