Compare commits
10 commits
fc76c927ca
...
d00b98edb9
| Author | SHA1 | Date | |
|---|---|---|---|
| d00b98edb9 | |||
| 8bf37d0d1a | |||
| 0da69454c7 | |||
| 822cf12d59 | |||
| a888888c9d | |||
| 6f265c3ec5 | |||
| b483dcf503 | |||
| 06a7f51805 | |||
| 04d2b6b93b | |||
| 9e3dce14a2 |
11 changed files with 110 additions and 85 deletions
|
|
@ -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"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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))?;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
28
src/lib.rs
28
src/lib.rs
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue