isorun 0.1.0.pre-x86_64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ use deno_core::error::AnyError;
2
+ use magnus::r_hash::ForEach;
3
+ use magnus::value::{Qfalse, Qtrue};
4
+ use magnus::{
5
+ Integer, RArray, RFloat, RHash, RString, RStruct, Symbol, Value, QFALSE,
6
+ QNIL, QTRUE,
7
+ };
8
+ use v8::{Array, GetPropertyNamesArgs, HandleScope, Local, Object};
9
+
10
+ pub fn convert_v8_to_ruby(
11
+ value: Local<v8::Value>,
12
+ scope: &mut HandleScope,
13
+ ) -> Result<Value, AnyError> {
14
+ if value.is_null() {
15
+ return Ok(Value::from(QNIL));
16
+ }
17
+
18
+ if value.is_int32() {
19
+ return Ok(Value::from(Integer::from_i64(
20
+ value.int32_value(scope).unwrap() as i64,
21
+ )));
22
+ }
23
+
24
+ if value.is_number() {
25
+ return Ok(Value::from(
26
+ RFloat::from_f64(value.number_value(scope).unwrap()).unwrap(),
27
+ ));
28
+ }
29
+
30
+ if value.is_true() {
31
+ return Ok(Value::from(QTRUE));
32
+ }
33
+
34
+ if value.is_false() {
35
+ return Ok(Value::from(QFALSE));
36
+ }
37
+
38
+ if value.is_string() {
39
+ let raw = value.to_rust_string_lossy(scope);
40
+ return Ok(Value::from(RString::from(raw)));
41
+ }
42
+
43
+ if value.is_array() {
44
+ let arr = Local::<Array>::try_from(value).unwrap();
45
+ let length = arr.length();
46
+ let r_arr = RArray::with_capacity(length as usize);
47
+ for i in 0..length {
48
+ let raw = arr.get_index(scope, i).unwrap();
49
+ let val = convert_v8_to_ruby(raw, scope).unwrap();
50
+ r_arr.push(val).expect("cannot add item to array");
51
+ }
52
+ return Ok(Value::from(r_arr));
53
+ }
54
+
55
+ if value.is_object() {
56
+ let obj = Local::<Object>::try_from(value).unwrap();
57
+ let properties = obj
58
+ .get_own_property_names(scope, GetPropertyNamesArgs::default())
59
+ .unwrap();
60
+ let length = properties.length();
61
+ let r_hash = RHash::new();
62
+ for i in 0..length {
63
+ let raw_key = properties.get_index(scope, i).unwrap();
64
+ let raw_val = obj.get(scope, raw_key).unwrap();
65
+ let key = convert_v8_to_ruby(raw_key, scope).unwrap();
66
+ let val = convert_v8_to_ruby(raw_val, scope).unwrap();
67
+ r_hash.aset(key, val).expect("cannot set item to hash");
68
+ }
69
+ return Ok(Value::from(r_hash));
70
+ }
71
+
72
+ Ok(Value::from(QNIL))
73
+ }
74
+
75
+ pub fn convert_ruby_to_v8<'s>(
76
+ value: Value,
77
+ scope: &mut HandleScope<'s>,
78
+ ) -> Result<Local<'s, v8::Value>, AnyError> {
79
+ if value.is_nil() {
80
+ return Ok(v8::null(scope).into());
81
+ }
82
+
83
+ if let Some(v) = Qtrue::from_value(value) {
84
+ return Ok(v8::Boolean::new(scope, v.to_bool()).into());
85
+ }
86
+
87
+ if let Some(v) = Qfalse::from_value(value) {
88
+ return Ok(v8::Boolean::new(scope, v.to_bool()).into());
89
+ }
90
+
91
+ if let Some(v) = Symbol::from_value(value) {
92
+ return Ok(v8::String::new(scope, v.to_string().as_str())
93
+ .unwrap()
94
+ .into());
95
+ }
96
+
97
+ if let Some(v) = Integer::from_value(value) {
98
+ return Ok(v8::Integer::new(scope, v.to_i32().unwrap()).into());
99
+ }
100
+
101
+ if let Some(v) = RFloat::from_value(value) {
102
+ return Ok(v8::Number::new(scope, v.to_f64()).into());
103
+ }
104
+
105
+ if let Some(v) = RString::from_value(value) {
106
+ return Ok(v8::String::new(scope, v.to_string().unwrap().as_str())
107
+ .unwrap()
108
+ .into());
109
+ }
110
+
111
+ if let Some(v) = RArray::from_value(value) {
112
+ let arr;
113
+ {
114
+ arr = Array::new(scope, v.len() as i32);
115
+ }
116
+
117
+ for (i, val) in v.each().enumerate() {
118
+ let v8_value;
119
+ {
120
+ v8_value = convert_ruby_to_v8(val.unwrap(), scope).unwrap();
121
+ }
122
+ arr.set_index(scope, i as u32, v8_value);
123
+ }
124
+ return Ok(arr.into());
125
+ }
126
+
127
+ if let Some(v) = RHash::from_value(value) {
128
+ let obj = Object::new(scope);
129
+ v.foreach(|key: Value, val: Value| {
130
+ let key = convert_ruby_to_v8(key, scope).unwrap();
131
+ let val = convert_ruby_to_v8(val, scope).unwrap();
132
+ obj.set(scope, key, val);
133
+
134
+ Ok(ForEach::Continue)
135
+ })
136
+ .expect("cannot convert hash into JavaScript object");
137
+
138
+ return Ok(obj.into());
139
+ }
140
+
141
+ if let Some(v) = RStruct::from_value(value) {
142
+ let obj = Object::new(scope);
143
+ for member in v.members().unwrap() {
144
+ let key = member.to_string();
145
+ let val = v.getmember::<&str, Value>(key.as_str()).unwrap();
146
+ let v8_key = v8::String::new(scope, key.as_str()).unwrap();
147
+ let v8_val = convert_ruby_to_v8(val, scope).unwrap();
148
+
149
+ obj.set(scope, v8_key.into(), v8_val);
150
+ }
151
+
152
+ return Ok(obj.into());
153
+ }
154
+
155
+ Ok(v8::null(scope).into())
156
+ }
@@ -0,0 +1,3 @@
1
+ pub(crate) mod module;
2
+ pub(crate) mod module_item;
3
+ pub(crate) mod worker;
@@ -0,0 +1,51 @@
1
+ use crate::js::module_item::{Function, ModuleItem, Value as JsValue};
2
+ use crate::js::worker::WORKER;
3
+ use deno_core::error::AnyError;
4
+ use deno_core::{JsRealm, ModuleId};
5
+ use std::cell::RefCell;
6
+ use std::rc::Rc;
7
+ use v8::{Global, Local, Value};
8
+
9
+ pub(crate) struct Module {
10
+ pub(crate) id: ModuleId,
11
+ pub(crate) realm: Rc<RefCell<JsRealm>>,
12
+ }
13
+
14
+ impl Module {
15
+ pub(crate) fn import(
16
+ &self,
17
+ export_name: &str,
18
+ ) -> Result<ModuleItem, AnyError> {
19
+ WORKER.with(|worker| {
20
+ let namespace = {
21
+ let mut worker = worker.worker.borrow_mut();
22
+ worker.js_runtime.get_module_namespace(self.id).unwrap()
23
+ };
24
+
25
+ let realm = self.realm.borrow();
26
+
27
+ let mut worker = worker.worker.borrow_mut();
28
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
29
+
30
+ let namespace = Local::new(&mut scope, namespace);
31
+
32
+ let export_name = v8::String::new(&mut scope, export_name).unwrap();
33
+
34
+ let binding =
35
+ namespace.get(&mut scope, export_name.into()).unwrap();
36
+ let global_binding = Global::<Value>::new(&mut scope, binding);
37
+
38
+ if binding.is_function() {
39
+ Ok(ModuleItem::Function(Function {
40
+ binding: global_binding,
41
+ realm: self.realm.clone(),
42
+ }))
43
+ } else {
44
+ Ok(ModuleItem::Value(JsValue {
45
+ binding: global_binding,
46
+ realm: self.realm.clone(),
47
+ }))
48
+ }
49
+ })
50
+ }
51
+ }
@@ -0,0 +1,71 @@
1
+ use crate::js::worker::WORKER;
2
+ use deno_core::JsRealm;
3
+ use magnus::gvl::without_gvl;
4
+ use std::cell::RefCell;
5
+ use std::ops::Deref;
6
+ use std::rc::Rc;
7
+ use v8::Global;
8
+
9
+ pub(crate) enum ModuleItem {
10
+ Value(Value),
11
+ Function(Function),
12
+ }
13
+
14
+ pub(crate) struct Function {
15
+ pub(crate) binding: Global<v8::Value>,
16
+ pub(crate) realm: Rc<RefCell<JsRealm>>,
17
+ }
18
+
19
+ impl Function {
20
+ pub(crate) fn call(
21
+ &self,
22
+ args: &[Global<v8::Value>],
23
+ ) -> Result<magnus::Value, magnus::Error> {
24
+ WORKER.with(|worker| {
25
+ let realm = self.realm.borrow();
26
+ let realm = realm.deref();
27
+ worker
28
+ .runtime
29
+ // we block here instead of the worker, due to a refcell issue
30
+ // when borrowing within an await
31
+ .block_on(worker.call(realm, &self.binding, args))
32
+ })
33
+ }
34
+
35
+ pub(crate) fn call_without_gvl(
36
+ &self,
37
+ args: &[Global<v8::Value>],
38
+ ) -> Result<magnus::Value, magnus::Error> {
39
+ WORKER.with(|worker| {
40
+ let realm = self.realm.borrow();
41
+ let realm = realm.deref();
42
+ let result = without_gvl(
43
+ |gvl_context| {
44
+ worker.ruby_context.replace(Some(gvl_context));
45
+ let result = worker
46
+ .runtime
47
+ // we block here instead of the worker, due to a refcell issue
48
+ // when borrowing within an await
49
+ .block_on(worker.call(realm, &self.binding, args));
50
+ worker.ruby_context.replace(None);
51
+ result
52
+ },
53
+ None::<fn()>,
54
+ );
55
+ result.0.unwrap()
56
+ })
57
+ }
58
+ }
59
+
60
+ pub(crate) struct Value {
61
+ pub(crate) binding: Global<v8::Value>,
62
+ pub(crate) realm: Rc<RefCell<JsRealm>>,
63
+ }
64
+
65
+ impl Value {
66
+ pub(crate) fn to_ruby(&self) -> Option<magnus::Value> {
67
+ let realm = self.realm.borrow();
68
+ let realm = realm.deref();
69
+ WORKER.with(|worker| worker.to_ruby(realm, &self.binding))
70
+ }
71
+ }
@@ -0,0 +1,265 @@
1
+ use crate::isorun::utils::{convert_ruby_to_v8, convert_v8_to_ruby};
2
+ use deno_core::error::AnyError;
3
+ use deno_core::serde_v8::from_v8;
4
+ use deno_core::{op, serde_v8, Extension, FsModuleLoader, JsRealm, ModuleId};
5
+ use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
6
+ use deno_runtime::permissions::Permissions;
7
+ use deno_runtime::worker::{MainWorker, WorkerOptions};
8
+ use deno_runtime::BootstrapOptions;
9
+ use deno_web::BlobStore;
10
+ use magnus::block::Proc;
11
+ use magnus::gvl::GVLContext;
12
+ use magnus::{Error, Value};
13
+ use std::borrow::BorrowMut;
14
+ use std::cell::RefCell;
15
+ use std::collections::{HashMap, HashSet};
16
+ use std::path::Path;
17
+ use std::rc::Rc;
18
+ use std::string::ToString;
19
+ use std::sync::Arc;
20
+ use tokio::runtime::Runtime;
21
+ use v8::{Global, Local};
22
+
23
+ fn get_error_class_name(e: &AnyError) -> &'static str {
24
+ deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
25
+ }
26
+
27
+ const USER_AGENT: &str = "isorun";
28
+
29
+ pub(crate) struct Worker {
30
+ pub(crate) runtime: Runtime,
31
+ pub(crate) worker: RefCell<MainWorker>,
32
+ module_map: RefCell<HashMap<String, ModuleId>>,
33
+ pub(crate) ruby_context: RefCell<Option<GVLContext>>,
34
+ pub(crate) ruby_receiver: RefCell<Option<Proc>>,
35
+ }
36
+
37
+ impl Worker {
38
+ pub(crate) fn create_realm(&self) -> Result<JsRealm, AnyError> {
39
+ let mut worker = self.worker.borrow_mut();
40
+ worker.js_runtime.create_realm()
41
+ }
42
+
43
+ pub(crate) fn load_module(&self, path: &str) -> Result<ModuleId, AnyError> {
44
+ let mut module_map = self.module_map.borrow_mut();
45
+ if module_map.contains_key(path) {
46
+ return Ok(*module_map.get(path).unwrap());
47
+ }
48
+
49
+ let module_id = {
50
+ let mut worker = self.worker.borrow_mut();
51
+
52
+ let module_specifier =
53
+ deno_core::resolve_url_or_path(path).unwrap();
54
+ let module_id = self
55
+ .runtime
56
+ .block_on(worker.preload_side_module(&module_specifier))?;
57
+ self.runtime.block_on(worker.evaluate_module(module_id))?;
58
+
59
+ module_id
60
+ };
61
+
62
+ module_map.insert(path.to_string(), module_id);
63
+
64
+ Ok(module_id)
65
+ }
66
+
67
+ pub(crate) async fn call(
68
+ &self,
69
+ realm: &JsRealm,
70
+ callee: &Global<v8::Value>,
71
+ args: &[Global<v8::Value>],
72
+ ) -> Result<Value, Error> {
73
+ let promise = {
74
+ let mut worker = self.worker.borrow_mut();
75
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
76
+
77
+ let callee = Local::<v8::Value>::new(&mut scope, callee);
78
+ let callee = Local::<v8::Function>::try_from(callee).unwrap();
79
+
80
+ let mut local_args: Vec<Local<v8::Value>> = vec![];
81
+ for arg in args {
82
+ let local_arg = Local::<v8::Value>::new(&mut scope, arg);
83
+ local_args.push(local_arg);
84
+ }
85
+ let receiver = v8::undefined(scope.borrow_mut());
86
+ let promise = callee
87
+ .call(&mut scope, receiver.into(), local_args.as_slice())
88
+ .unwrap();
89
+ Global::<v8::Value>::new(&mut scope, promise)
90
+ };
91
+
92
+ let value = {
93
+ let mut worker = self.worker.borrow_mut();
94
+ worker.js_runtime.resolve_value(promise).await.unwrap()
95
+ };
96
+
97
+ let value = self.to_ruby(realm, &value).unwrap();
98
+
99
+ Ok(value)
100
+ }
101
+
102
+ pub(crate) fn to_ruby(
103
+ &self,
104
+ realm: &JsRealm,
105
+ value: &Global<v8::Value>,
106
+ ) -> Option<Value> {
107
+ let mut worker = self.worker.borrow_mut();
108
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
109
+ let value = Local::new(&mut scope, value);
110
+ let result = convert_v8_to_ruby(value, &mut scope);
111
+
112
+ match result {
113
+ Ok(v) => Some(v),
114
+ Err(_) => None,
115
+ }
116
+ }
117
+
118
+ pub(crate) fn to_v8(
119
+ &self,
120
+ realm: &JsRealm,
121
+ value: Value,
122
+ ) -> Option<Global<v8::Value>> {
123
+ let mut worker = self.worker.borrow_mut();
124
+ let mut scope = realm.handle_scope(worker.js_runtime.v8_isolate());
125
+ let value = convert_ruby_to_v8(value, &mut scope).unwrap();
126
+ let value = Global::<v8::Value>::new(&mut scope, value);
127
+
128
+ Some(value)
129
+ }
130
+
131
+ fn send(&self, value: Value) -> Result<Value, Error> {
132
+ // we need to deref the receiver as mut, as it is behind an Option
133
+ if let (Some(ctx), Some(rec)) = (
134
+ self.ruby_context.borrow_mut().as_mut(),
135
+ self.ruby_receiver.borrow_mut().as_mut(),
136
+ ) {
137
+ ctx.with_gvl(|| {
138
+ let args: (Value,) = (value,);
139
+ rec.call::<(Value,), Value>(args)
140
+ })?
141
+ } else {
142
+ Err(Error::runtime_error(
143
+ "Cannot send to ruby. Is the ruby receiver and context initialized and set?",
144
+ ))
145
+ }
146
+ }
147
+ }
148
+
149
+ impl Default for Worker {
150
+ fn default() -> Self {
151
+ let module_loader = Rc::new(FsModuleLoader);
152
+ let create_web_worker_cb = Arc::new(|_| {
153
+ todo!("Web workers are not supported in the example");
154
+ });
155
+ let web_worker_event_cb = Arc::new(|_| {
156
+ todo!("Web workers are not supported in the example");
157
+ });
158
+
159
+ let extension_send = Extension::builder()
160
+ .ops(vec![op_send_to_ruby::decl()])
161
+ .build();
162
+ let mut extensions = vec![extension_send];
163
+
164
+ let options = WorkerOptions {
165
+ bootstrap: BootstrapOptions {
166
+ args: vec![],
167
+ cpu_count: 1,
168
+ debug_flag: false,
169
+ enable_testing_features: false,
170
+ locale: v8::icu::get_language_tag(),
171
+ location: None,
172
+ no_color: false,
173
+ is_tty: false,
174
+ runtime_version: "x".to_string(),
175
+ ts_version: "x".to_string(),
176
+ unstable: false,
177
+ user_agent: USER_AGENT.to_string(),
178
+ inspect: false,
179
+ },
180
+ extensions: std::mem::take(&mut extensions),
181
+ startup_snapshot: None,
182
+ unsafely_ignore_certificate_errors: None,
183
+ root_cert_store: None,
184
+ seed: None,
185
+ source_map_getter: None,
186
+ format_js_error_fn: None,
187
+ web_worker_preload_module_cb: web_worker_event_cb.clone(),
188
+ web_worker_pre_execute_module_cb: web_worker_event_cb,
189
+ create_web_worker_cb,
190
+ maybe_inspector_server: None,
191
+ should_break_on_first_statement: false,
192
+ module_loader,
193
+ npm_resolver: None,
194
+ get_error_class_fn: Some(&get_error_class_name),
195
+ cache_storage_dir: None,
196
+ origin_storage_dir: None,
197
+ blob_store: BlobStore::default(),
198
+ broadcast_channel: InMemoryBroadcastChannel::default(),
199
+ shared_array_buffer_store: None,
200
+ compiled_wasm_module_store: None,
201
+ stdio: Default::default(),
202
+ };
203
+
204
+ // todo: we don't use the main module at all, but it could be used as an
205
+ // entry point for "eval" JavaScript.
206
+ let js_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/call.js");
207
+ let main_module =
208
+ deno_core::resolve_path(&js_path.to_string_lossy()).unwrap();
209
+ let permissions = Permissions::allow_all();
210
+ let mut worker = MainWorker::bootstrap_from_options(
211
+ main_module.clone(),
212
+ permissions,
213
+ options,
214
+ );
215
+
216
+ let runtime = tokio::runtime::Builder::new_current_thread()
217
+ .enable_all()
218
+ .build()
219
+ .unwrap();
220
+
221
+ runtime.block_on(async {
222
+ let module_id =
223
+ worker.preload_main_module(&main_module).await.unwrap();
224
+ worker
225
+ .evaluate_module(module_id)
226
+ .await
227
+ .expect("cannot evaluate core module");
228
+ });
229
+
230
+ Worker {
231
+ runtime,
232
+ module_map: RefCell::from(HashMap::default()),
233
+ worker: RefCell::from(worker),
234
+ ruby_context: RefCell::from(None),
235
+ ruby_receiver: RefCell::from(None),
236
+ }
237
+ }
238
+ }
239
+
240
+ thread_local! {
241
+ pub(crate) static WORKER: Worker = Worker::default();
242
+ pub(crate) static MODULE_MAP: HashSet<ModuleId> = HashSet::default();
243
+ }
244
+
245
+ #[allow(clippy::extra_unused_lifetimes)]
246
+ #[op(v8)]
247
+ fn op_send_to_ruby<'a>(
248
+ // do not remove the v8:: prefix, otherwise the macro complains
249
+ scope: &mut v8::HandleScope,
250
+ data: serde_v8::Value<'a>,
251
+ ) -> Result<serde_v8::Value<'a>, AnyError> {
252
+ let value = convert_v8_to_ruby(data.v8_value, scope)?;
253
+
254
+ WORKER.with(|worker| {
255
+ worker
256
+ .send(value)
257
+ .map(|v| {
258
+ let v = convert_ruby_to_v8(v, scope).unwrap();
259
+ from_v8(scope, v).unwrap()
260
+ })
261
+ .map_err(|error| {
262
+ AnyError::msg(format!("failed to send to ruby: {}", error))
263
+ })
264
+ })
265
+ }
@@ -0,0 +1,51 @@
1
+ use crate::isorun::context::Context;
2
+ use isorun::configure::set_receiver;
3
+ use isorun::function::Function;
4
+ use isorun::module::Module;
5
+ use magnus::{define_module, function, method, Error, Module as M, Object};
6
+
7
+ mod isorun;
8
+ mod js;
9
+
10
+ #[magnus::init]
11
+ fn init() -> Result<(), Error> {
12
+ let root = define_module("Isorun").expect("cannot define module: Isorun");
13
+
14
+ root.define_module_function("receiver=", function!(set_receiver, 1))
15
+ .expect("cannot define module function: receiver=");
16
+
17
+ let context = root
18
+ .define_class("Context", Default::default())
19
+ .expect("cannot define class: Isorun::Context");
20
+ context
21
+ .define_singleton_method("new", function!(Context::new, 0))
22
+ .expect("cannot define singelton method: new");
23
+ context
24
+ .define_method("load", method!(Context::load, 1))
25
+ .expect("cannot load module");
26
+
27
+ let module = root
28
+ .define_class("Module", Default::default())
29
+ .expect("cannot define class: Isorun::Module");
30
+ module
31
+ .define_private_method("id", method!(Module::id, 0))
32
+ .expect("cannot define method: module_id");
33
+ module
34
+ .define_method("import", method!(Module::import, 1))
35
+ .expect("cannot define method: import");
36
+
37
+ let function = root
38
+ .define_class("Function", Default::default())
39
+ .expect("cannot define class: Isorun::Function");
40
+ function
41
+ .define_method("call", method!(Function::call, -1))
42
+ .expect("cannot define method: call");
43
+ function
44
+ .define_method(
45
+ "call_without_gvl",
46
+ method!(Function::call_without_gvl, -1),
47
+ )
48
+ .expect("cannot define method: call_without_gvl");
49
+
50
+ Ok(())
51
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Isorun
4
+ class Config
5
+ # Abstract base class for isorun and it's extensions configuration
6
+ # builder. Instantiates and validates gem configuration.
7
+ #
8
+ class AbstractBuilder
9
+ attr_reader :config
10
+
11
+ # @param [Class] config class
12
+ #
13
+ def initialize(config = Config.new, &block)
14
+ @config = config
15
+ instance_eval(&block)
16
+ end
17
+
18
+ # Builds and validates configuration.
19
+ #
20
+ # @return [Isorun::Config] config instance
21
+ #
22
+ def build
23
+ @config.validate! if @config.respond_to?(:validate!)
24
+ @config
25
+ end
26
+ end
27
+ end
28
+ end