Skip to main content

quickjs_runtime/
lib.rs

1//! # quickjs_runtime
2//! This crate consists of two main parts:
3//! * thread-safe utils and wrappers
4//!   you can call these from any thread, all logic is directed to a single worker-thread(EventLoop) which invokes the quickjs API
5//! * quickjs bindings and utils
6//!   these talk to the quickjs API directly and need to run in the same thread as the Runtime
7//!
8//! ## Noteworthy structs
9//!
10//! These are the structs you'll use the most
11//!
12//! | Thread safe (Facades) | Runtime Thread-local (Adapters) |
13//! | --- | --- |
14//! | [QuickJsRuntimeFacade](facades/struct.QuickJsRuntimeFacade.html) the 'starting point' | [QuickJsRuntimeAdapter](quickjsruntimeadapter/struct.QuickJsRuntimeAdapter.html) the wrapper for all things quickjs |
15//! | - | [QuickJsRealmAdapter](quickjsrealmadapter/struct.QuickJsRealmAdapter.html) a realm or context |
16//! | [JsValueFacade](https://hirofa.github.io/utils/hirofa_utils/js_utils/facades/values/enum.JsValueFacade.html) copy of- or reference to a value in the JsRuntimeAdapter | [QuickJsValueAdapter](quickjsvalueadapter/struct.QuickJsValueAdapter.html) reference counting pointer to a Value |
17//!
18//! ## Doing something in the runtime worker thread
19//!
20//! You always start with building a new [QuickjsRuntimeFacade](facades/struct.QuickjsRuntimeFacade.html)
21//!
22//! ```dontrun
23//! use quickjs_runtime::builder::QuickJsRuntimeBuilder;
24//! let rt: JsRuntimeFacade = QuickJsRuntimeBuilder::new().js_build();
25//! ```
26//!
27//! [QuickJsRuntimeFacade](facades/struct.QuickJsRuntimeFacade.html) has plenty public methods you can check out but one of the things you'll need to understand is how to communicate with the [QuickJsRuntimeAdapter](quickjsruntimeadapter/struct.QuickJsRuntimeAdapter.html) and the [QuickJsRealmAdapter](quickjsrealmadapter/struct.QuickJsRealmAdapter.html)
28//! This is done by adding a job to the [EventLoop](https://hirofa.github.io/utils/hirofa_utils/eventloop/struct.EventLoop.html) of the [QuickJsRuntimeFacade](facades/struct.QuickJsRuntimeFacade.html)
29//!
30//! ```dontrun
31//! // with the first Option you may specify which realm to use, None indicates the default or main realm
32//! let res = rt.loop_realm(None, |rt: QuickJsRuntimeAdapter, realm: QuickJsRealmAdapter| {
33//!    // this will run in the Worker thread, here we can use the Adapters
34//!    // since we passed None as realm the realm adapter will be the "main" realm
35//!    return true;
36//! }).await;
37//! ```
38//! All the non-sync functions return a Future so you can .await them from async functions.
39//!
40//! In order to do something and get the result synchronously you can use the sync variant
41//! ```dontrun
42//! use quickjs_runtime::quickjsruntime::QuickJsRuntime;
43//! let res = rt.loop_realm_sync(None, |rt, realm| {
44//!    // this will run in the Worker thread, here we can use the quickjs API
45//!    return 1;
46//! });
47//! ```
48//!
49//! One last thing you need to know is how to pass values from the js engine out of the worker thread
50//!
51//! This is where the JsValueFacade comes in
52//!
53//! ```dontrun
54//!
55//! // init a simple function
56//! rt.eval(Script::new("init_func.js", "globalThis.myObj = {someMember: {someFunction: function(input){return(input + " > hello rust!");}}};")).await;
57//!
58//! // create an input variable by using one of the constructor methods of the JsValueFacade
59//! let input_facade = JsValueFacade::new_str("hello js!");
60//! // move it into a closure which will run in the worker thread
61//! let res = rt.loop_realm(None, move |rt: JsRuntimeAdapter, realm: JsRealmAdapter| {
62//!    // convert the input JsValueFacade to JsValueAdapter
63//!    let input_adapter = realm.from_js_value_facade(input_facade)?;
64//!    // call myObj.someMember.someFunction();
65//!    let result_adapter = realm.invoke_function_by_name(&["myObj", "someMember"], "someFunction", &[input_adapter])?;
66//!    // convert adapter to facade again so it may move out of the worker thread
67//!    return realm.to_js_value_facade(&result_adapter);
68//! }).await;
69//! assert_eq!(res.get_str(), "hello_js! > hello rust!");
70//! ```
71//!
72//! For more details and examples, please explore the packages below
73extern crate core;
74extern crate lazy_static;
75
76pub mod builder;
77pub mod facades;
78#[cfg(any(
79    feature = "settimeout",
80    feature = "setinterval",
81    feature = "console",
82    feature = "setimmediate"
83))]
84pub mod features;
85pub mod jsutils;
86pub mod quickjs_utils;
87pub mod quickjsrealmadapter;
88pub mod quickjsruntimeadapter;
89pub mod quickjsvalueadapter;
90pub mod reflection;
91#[cfg(feature = "typescript")]
92pub mod typescript;
93pub mod values;
94
95pub use libquickjs_sys;
96
97#[cfg(test)]
98pub mod tests {
99    use crate::builder::QuickJsRuntimeBuilder;
100    use crate::facades::tests::init_test_rt;
101    use crate::facades::QuickJsRuntimeFacade;
102    use crate::jsutils::jsproxies::JsProxy;
103    use crate::jsutils::{JsError, Script};
104    use crate::quickjsrealmadapter::QuickJsRealmAdapter;
105    use crate::values::{JsValueConvertable, JsValueFacade};
106    use futures::executor::block_on;
107    use std::thread;
108    use std::time::Duration;
109
110    #[test]
111    fn test_examples() {
112        let rt = QuickJsRuntimeBuilder::new().build();
113        let outcome = block_on(run_examples(&rt));
114        if outcome.is_err() {
115            log::error!("an error occured: {}", outcome.err().unwrap());
116        }
117        log::info!("done");
118    }
119
120    #[test]
121    fn test_st() {
122        let rt = init_test_rt();
123
124        let _res = rt
125            .eval_sync(
126                None,
127                Script::new(
128                    "t.js",
129                    r#"
130            
131            async function a(){
132                await b();
133            }
134            
135            async function b(){
136                throw Error("poof");
137            }
138            
139            a().then(() => {
140                console.log("a done");
141            }).catch(() => {
142                console.log("a error");
143            });
144            
145            1
146        "#,
147                ),
148            )
149            .expect("script failed");
150        thread::sleep(Duration::from_secs(1));
151    }
152
153    async fn take_long() -> i32 {
154        std::thread::sleep(Duration::from_millis(500));
155        537
156    }
157
158    async fn run_examples(rt: &QuickJsRuntimeFacade) -> Result<(), JsError> {
159        // ensure console.log calls get outputted
160        //simple_logging::log_to_stderr(LevelFilter::Info);
161
162        // do a simple eval on the main realm
163        let eval_res = rt.eval(None, Script::new("simple_eval.js", "2*7;")).await?;
164        log::info!("simple eval:{}", eval_res.get_i32());
165
166        // invoke a JS method from rust
167
168        let meth_res = rt
169            .invoke_function(None, &["Math"], "round", vec![12.321.to_js_value_facade()])
170            .await?;
171        log::info!("Math.round(12.321) = {}", meth_res.get_i32());
172
173        // add a rust function to js as a callback
174
175        let cb = JsValueFacade::new_callback(|args| {
176            let a = args[0].get_i32();
177            let b = args[1].get_i32();
178            log::info!("rust cb was called with a:{} and b:{}", a, b);
179            Ok(JsValueFacade::Null)
180        });
181        rt.invoke_function(
182            None,
183            &[],
184            "setTimeout",
185            vec![
186                cb,
187                10.to_js_value_facade(),
188                12.to_js_value_facade(),
189                13.to_js_value_facade(),
190            ],
191        )
192        .await?;
193        std::thread::sleep(Duration::from_millis(20));
194        log::info!("rust cb should have been called by now");
195
196        // create simple proxy class with an async function
197        rt.loop_realm_sync(None, |_rt_adapter, realm_adapter| {
198            let proxy = JsProxy::new()
199                .namespace(&["com", "mystuff"])
200                .name("MyProxy")
201                .static_method(
202                    "doSomething",
203                    |_rt_adapter, realm_adapter: &QuickJsRealmAdapter, _args| {
204                        realm_adapter.create_resolving_promise_async(
205                            async { Ok(take_long().await) },
206                            |realm_adapter, producer_result| {
207                                realm_adapter.create_i32(producer_result)
208                            },
209                        )
210                    },
211                );
212            realm_adapter
213                .install_proxy(proxy, true)
214                .expect("could not install proxy");
215        });
216
217        rt.eval(
218            None,
219            Script::new(
220                "testMyProxy.js",
221                "async function a() {\
222                            console.log('a called at %s ms', new Date().getTime());\
223                            let res = await com.mystuff.MyProxy.doSomething();\
224                            console.log('a got result %s at %s ms', res, new Date().getTime());\
225                           }; a();",
226            ),
227        )
228        .await?;
229        std::thread::sleep(Duration::from_millis(600));
230        log::info!("a should have been called by now");
231
232        Ok(())
233    }
234}