moduforge_rules_engine/handler/function/module/
console.rs

1use 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}