moduforge_rules_engine/handler/function/module/
console.rs1use std::cell::RefCell;
2use std::future::Future;
3use std::pin::Pin;
4use std::time::{Duration, Instant};
5
6use crate::handler::function::error::{FunctionResult, ResultExt};
7use crate::handler::function::listener::{RuntimeEvent, RuntimeListener};
8use rquickjs::prelude::Rest;
9use rquickjs::{Ctx, Object, Value};
10use serde::{Deserialize, Serialize};
11
12pub struct ConsoleListener;
13
14impl RuntimeListener for ConsoleListener {
15 fn on_event<'js>(
16 &self,
17 ctx: Ctx<'js>,
18 event: RuntimeEvent,
19 ) -> Pin<Box<dyn Future<Output = FunctionResult> + 'js>> {
20 Box::pin(async move {
21 match event {
22 RuntimeEvent::Startup => Console::init(&ctx)?,
23 RuntimeEvent::SoftReset => Console::init(&ctx)?,
24 }
25
26 Ok(())
27 })
28 }
29}
30
31#[derive(Serialize, Deserialize, Clone)]
32#[serde(rename_all = "camelCase")]
33pub struct Log {
34 pub lines: Vec<String>,
35 pub ms_since_run: usize,
36}
37
38#[derive(rquickjs::class::Trace, rquickjs::JsLifetime, Clone)]
39#[rquickjs::class]
40pub struct Console {
41 #[qjs(skip_trace)]
42 pub logs: RefCell<Vec<Log>>,
43 #[qjs(skip_trace)]
44 created_at: Instant,
45}
46
47#[rquickjs::methods(rename_all = "camelCase")]
48impl Console {
49 fn new() -> Self {
50 Self { logs: Default::default(), created_at: Instant::now() }
51 }
52
53 #[qjs(skip)]
54 pub fn init(ctx: &Ctx) -> rquickjs::Result<()> {
55 ctx.globals().set("console", Self::new())?;
56 Ok(())
57 }
58
59 #[qjs(skip)]
60 pub fn from_context(ctx: &Ctx) -> rquickjs::Result<Self> {
61 let obj: Self = ctx.globals().get("console")?;
62 Ok(obj)
63 }
64
65 pub fn log<'js>(
66 &self,
67 ctx: Ctx<'js>,
68 args: Rest<Value<'js>>,
69 ) -> rquickjs::Result<()> {
70 let config: Object = ctx.globals().get("config").or_throw(&ctx)?;
71 let trace: bool = config.get("trace").or_throw(&ctx)?;
72 if !trace {
73 return Ok(());
74 }
75
76 let step1 =
77 args.0
78 .into_iter()
79 .map(|arg| ctx.json_stringify(arg))
80 .collect::<Result<Vec<Option<rquickjs::String<'js>>>, _>>()?;
81
82 let step2 = step1
83 .into_iter()
84 .map(|s| s.map(|i| i.to_string()).transpose())
85 .collect::<Result<Vec<Option<String>>, _>>()?;
86
87 let step3 = step2
88 .into_iter()
89 .map(|s| s.unwrap_or_default())
90 .collect::<Vec<String>>();
91
92 let mut logs = self.logs.borrow_mut();
93 logs.push(Log {
94 lines: step3,
95 ms_since_run: self.created_at.elapsed().as_millis() as usize,
96 });
97
98 Ok(())
99 }
100
101 pub async fn sleep(
102 &self,
103 duration_ms: u64,
104 ) -> rquickjs::Result<()> {
105 tokio::time::sleep(Duration::from_millis(duration_ms)).await;
106 Ok(())
107 }
108}