Skip to content
Draft
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 20 additions & 2 deletions assets/examples/ruby/promises.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
get_player_name.and_then do |name|
puts name
def async_fun
async do
a = get_player_name
b = a
puts '0'
puts a.await
puts '1'
u = get_player_name
puts b.await
puts '2'
z = get_player_name
puts z
puts z.await
puts "end"
end
end

async_fun.await
puts "after await"

quit
22 changes: 13 additions & 9 deletions examples/ruby/promises.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_scripting::<RubyRuntime>(|builder| {
builder.add_function(
String::from("get_player_name"),
|player_names: Query<&Name, With<Player>>| {
player_names
.single()
.expect("Missing player_names")
.to_string()
},
);
builder
.add_function(
String::from("get_player_name"),
|player_names: Query<&Name, With<Player>>| {
player_names
.single()
.expect("Missing player_names")
.to_string()
},
)
.add_function(String::from("quit"), |mut exit: EventWriter<AppExit>| {
exit.write(AppExit::Success);
});
})
.add_systems(Startup, startup)
.run();
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ pub trait Runtime: Resource + Default {
fn needs_rdynamic_linking() -> bool {
false
}

fn resume(&self, fiber: &Self::Value, value: &Self::Value);
}

pub trait FuncArgs<'a, V, R: Runtime> {
Expand Down
25 changes: 24 additions & 1 deletion src/promise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub(crate) struct PromiseInner<C: Send, V: Send> {
pub(crate) callbacks: Vec<PromiseCallback<C, V>>,
#[allow(deprecated)]
pub(crate) context: C,
pub(crate) resolved_value: Option<V>,
pub(crate) fibers: Vec<V>, // TODO: should htis be vec or option
}

/// A struct that represents a Promise.
Expand Down Expand Up @@ -51,12 +53,31 @@ impl<C: Clone + Send + 'static, V: Send + Clone> Promise<C, V> {
where
R: Runtime<Value = V, CallContext = C>,
{
let mut fibers: Vec<V> = vec![];
if let Ok(mut inner) = self.inner.lock() {
inner.resolve(runtime, val)?;
inner.resolved_value = Some(val.clone());
inner.resolve(runtime, val.clone())?;

for fiber in inner.fibers.drain(..) {
fibers.push(fiber);
}
}
for fiber in fibers {
runtime.resume(&fiber, &val.clone());
}
Ok(())
}

/// Register a fiber that will be resumed when the [Promise] is resolved.
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
pub(crate) fn await_promise(&mut self, fiber: V) {
let mut inner = self
.inner
.lock()
.expect("Failed to lock inner promise mutex");
inner.fibers.push(fiber);
}

/// Register a callback that will be called when the [Promise] is resolved.
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
pub(crate) fn then(&mut self, callback: V) -> Self {
Expand All @@ -65,8 +86,10 @@ impl<C: Clone + Send + 'static, V: Send + Clone> Promise<C, V> {
.lock()
.expect("Failed to lock inner promise mutex");
let following_inner = Arc::new(Mutex::new(PromiseInner {
fibers: vec![],
callbacks: vec![],
context: inner.context.clone(),
resolved_value: None,
}));

inner.callbacks.push(PromiseCallback {
Expand Down
80 changes: 74 additions & 6 deletions src/runtimes/ruby.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
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::{
Fiber,
typed_data::Inspect,
value::{self, Opaque},
};
use bevy::{
asset::Asset,
ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel},
Expand All @@ -19,7 +24,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::{
Expand Down Expand Up @@ -174,6 +179,19 @@ 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().as_value());
if let Some(value) = &promise.inner.try_lock().unwrap().resolved_value {
return ruby.get_inner(value.0);
}
promise.clone().await_promise(RubyValue(fiber)).into_value();
ruby.fiber_yield::<_, magnus::Value>(()).unwrap()
}

#[derive(Clone, Debug)]
#[magnus::wrap(class = "Bevy::Entity")]
pub struct BevyEntity(pub Entity);
Expand All @@ -195,6 +213,19 @@ impl TryConvert for BevyEntity {
#[magnus::wrap(class = "Bevy::Vec3")]
pub struct BevyVec3(pub Vec3);

pub fn async_function() {
let ruby = Ruby::get().unwrap();
let fiber = ruby
.fiber_new_from_fn(Default::default(), move |ruby, _args, _block| {
let p = ruby.block_proc().unwrap();
p.call::<_, value::Value>(()).unwrap();

Ok(())
})
.unwrap();
fiber.resume::<_, magnus::Value>(()).unwrap();
}

impl BevyVec3 {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self(Vec3::new(x, y, z))
Expand Down Expand Up @@ -266,12 +297,15 @@ 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))?;
vec3.define_method("x", method!(BevyVec3::x, 0))?;
vec3.define_method("y", method!(BevyVec3::y, 0))?;
vec3.define_method("z", method!(BevyVec3::z, 0))?;

ruby.define_global_function("async", function!(async_function, 0));
Ok::<(), ScriptingError>(())
}))
.expect("Failed to define builtin types");
Expand Down Expand Up @@ -392,10 +426,35 @@ impl Runtime for RubyRuntime {
) -> Result<Self::ScriptData, crate::ScriptingError> {
let script = script.0.clone();
self.execute_in_thread(Box::new(move |ruby: &Ruby| {
Self::with_current_entity(ruby, entity, || {
ruby.eval::<magnus::value::Value>(&script)
.map_err(<magnus::Error as Into<ScriptingError>>::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, 0);
};
// ruby.eval::<magnus::value::Value>(&script)
// .map_err(<magnus::Error as Into<ScriptingError>>::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::<Self::ScriptData, ScriptingError>(RubyScriptData)
}))
}
Expand Down Expand Up @@ -509,6 +568,15 @@ impl Runtime for RubyRuntime {
fn needs_rdynamic_linking() -> bool {
true
}

fn resume(&self, fiber: &Self::Value, value: &Self::Value) {
let fiber = fiber.clone();
let value = value.clone();
self.execute_in_thread(move |ruby| {
let fiber: Fiber = TryConvert::try_convert(ruby.get_inner(fiber.0)).unwrap();
fiber.resume::<_, magnus::Value>((value.0,));
});
}
}

pub mod magnus {
Expand Down
4 changes: 3 additions & 1 deletion src/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub(crate) fn init_callbacks<R: Runtime>(world: &mut World) -> Result<(), Script
move |context, params| {
let promise = Promise {
inner: Arc::new(Mutex::new(PromiseInner {
resolved_value: None,
fibers: vec![],
callbacks: vec![],
context,
})),
Expand All @@ -100,7 +102,7 @@ pub(crate) fn init_callbacks<R: Runtime>(world: &mut World) -> Result<(), Script
.expect("Failed to lock callback calls mutex");

calls.push(FunctionCallEvent {
promise: promise.clone(),
promise: promise.clone(), // TODO: dont clone?
params,
});
Ok(promise)
Expand Down
Loading