diff --git a/Cargo.toml b/Cargo.toml index 71cc86f..9dbd741 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ magnus = { version = "0.7.1", optional = true } rb-sys = { version = "0.9", default-features = false, features = ["link-ruby", "ruby-static"], optional = true } crossbeam-channel = "0.5.15" libc = "0.2.172" +tempfile = "3.20.0" [[example]] name = "call_function_from_rust_rhai" diff --git a/assets/examples/ruby/promises.rb b/assets/examples/ruby/promises.rb index dd0ecbe..9aeacb0 100644 --- a/assets/examples/ruby/promises.rb +++ b/assets/examples/ruby/promises.rb @@ -1,3 +1,2 @@ -get_player_name.and_then do |name| - puts name -end +puts get_player_name.await +quit diff --git a/examples/ruby/promises.rs b/examples/ruby/promises.rs index 1b3ed62..5afe115 100644 --- a/examples/ruby/promises.rs +++ b/examples/ruby/promises.rs @@ -9,15 +9,19 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_scripting::(|builder| { - builder.add_function( - String::from("get_player_name"), - |player_names: Query<&Name, With>| { - player_names - .single() - .expect("Missing player_names") - .to_string() - }, - ); + builder + .add_function( + String::from("get_player_name"), + |player_names: Query<&Name, With>| { + player_names + .single() + .expect("Missing player_names") + .to_string() + }, + ) + .add_function(String::from("quit"), |mut exit: EventWriter| { + exit.write(AppExit::Success); + }); }) .add_systems(Startup, startup) .run(); diff --git a/src/runtimes/ruby.rs b/src/runtimes/ruby.rs index 1ccccdd..1646d7f 100644 --- a/src/runtimes/ruby.rs +++ b/src/runtimes/ruby.rs @@ -1,11 +1,15 @@ use std::{ collections::HashMap, ffi::CString, + io::Write, sync::{Arc, Condvar, LazyLock, Mutex}, thread::{self, JoinHandle}, }; -use ::magnus::{typed_data::Inspect, value::Opaque}; +use ::magnus::{ + typed_data::Inspect, + value::{self, Opaque}, +}; use bevy::{ asset::Asset, ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel}, @@ -19,7 +23,7 @@ use magnus::{ value::{Lazy, ReprValue}, }; use magnus::{method, prelude::*}; -use rb_sys::{VALUE, ruby_init_stack}; +use rb_sys::{VALUE, rb_load, ruby_init_stack}; use serde::Deserialize; use crate::{ @@ -174,6 +178,25 @@ fn then(r_self: magnus::Value) -> magnus::Value { .into_value() } +fn await_promise(r_self: magnus::Value) -> magnus::Value { + let promise: &Promise<(), RubyValue> = + TryConvert::try_convert(r_self).expect("Couldn't convert self to Promise"); + let ruby = + Ruby::get().expect("Failed to get a handle to Ruby API when registering Promise callback"); + let fiber = Opaque::from(ruby.fiber_current()); + promise + .clone() + .then(RubyValue::new( + ruby.proc_from_fn(move |ruby, args, _| { + let fiber = ruby.get_inner(fiber); + fiber.resume::<_, magnus::Value>(args).unwrap(); + }) + .as_value(), + )) + .into_value(); + ruby.fiber_yield::<_, magnus::Value>(()).unwrap() +} + #[derive(Clone, Debug)] #[magnus::wrap(class = "Bevy::Entity")] pub struct BevyEntity(pub Entity); @@ -266,6 +289,7 @@ impl Default for RubyRuntime { let promise = module.define_class("Promise", ruby.class_object())?; promise.define_method("and_then", magnus::method!(then, 0))?; + promise.define_method("await", magnus::method!(await_promise, 0))?; let vec3 = module.define_class("Vec3", ruby.class_object())?; vec3.define_singleton_method("new", function!(BevyVec3::new, 3))?; @@ -392,10 +416,35 @@ impl Runtime for RubyRuntime { ) -> Result { let script = script.0.clone(); self.execute_in_thread(Box::new(move |ruby: &Ruby| { - Self::with_current_entity(ruby, entity, || { - ruby.eval::(&script) - .map_err(>::into) - })?; + let p = Opaque::from(ruby.proc_from_fn(move |ruby, _args, _block| { + Self::with_current_entity(ruby, entity, || { + let mut tmpfile = tempfile::NamedTempFile::new().unwrap(); + tmpfile.write(script.as_bytes()).unwrap(); + unsafe { + let file = rb_sys::rb_str_new_cstr( + CString::new(tmpfile.path().to_str().unwrap()) + .unwrap() + .into_raw(), + ); + rb_load(file, 1); + }; + // ruby.eval::(&script) + // .map_err(>::into) + Ok::<(), ScriptingError>(()) + }) + .unwrap(); + })); + let fiber = ruby + .fiber_new_from_fn(Default::default(), move |ruby, _args, _block| { + let p = ruby.get_inner(p); + + p.call::<_, value::Value>(()).unwrap(); + + Ok(()) + }) + .unwrap(); + fiber.resume::<_, value::Value>(()).unwrap(); + Ok::(RubyScriptData) })) }