Skip to main content

mumuprocess/
lib.rs

1// src/lib.rs (final, warnings removed)
2
3// If your code also has a "spawn" bridging or other tasks, it might store them
4// in `PROCESS_MANAGER`. For illustration, we keep it, but it's not strictly
5// necessary if you only want `process:info`.
6
7use mumu::{
8    parser::interpreter::Interpreter,
9    parser::types::{Value, FunctionValue},
10    parser::interpreter::apply_one_function_value,
11};
12use std::ffi::c_void;
13use std::{
14    sync::{
15        Arc,
16        Mutex,
17        mpsc::{channel, Receiver, Sender},
18        atomic::{AtomicUsize, Ordering},
19    },
20    collections::HashMap,
21};
22use lazy_static::lazy_static;
23use indexmap::IndexMap;
24use whoami;
25
26// A global counter so the test harness or others can track active tasks
27pub static ACTIVE_TASKS: AtomicUsize = AtomicUsize::new(0);
28
29// If you’re on Unix, we can get the actual UID; otherwise fallback to 0
30#[cfg(unix)]
31fn get_uid() -> i32 {
32    use nix::unistd::getuid;
33    getuid().as_raw() as i32
34}
35#[cfg(not(unix))]
36fn get_uid() -> i32 {
37    0
38}
39
40// We allow dead_code here because we don't construct these variants yet
41#[allow(dead_code)]
42enum ProcessMessage {
43    Ok(usize, Value),
44    Err(usize, String),
45}
46
47struct ProcessTask {
48    callback: Box<FunctionValue>,
49    done: bool,
50}
51
52struct ProcessManager {
53    // Renamed to underscore to avoid warnings about unread fields
54    _next_id: usize,
55    tasks: HashMap<usize, ProcessTask>,
56    _tx: Sender<ProcessMessage>,
57    rx: Receiver<ProcessMessage>,
58    active_count: AtomicUsize,
59}
60
61impl ProcessManager {
62    fn new() -> Self {
63        let (tx, rx) = channel();
64        Self {
65            _next_id: 0,
66            tasks: HashMap::new(),
67            _tx: tx,
68            rx,
69            active_count: AtomicUsize::new(0),
70        }
71    }
72
73    fn poll_events(&mut self, interp: &mut Interpreter) {
74        while let Ok(msg) = self.rx.try_recv() {
75            match msg {
76                ProcessMessage::Ok(id, data_val) => {
77                    if let Some(t) = self.tasks.get_mut(&id) {
78                        if !t.done {
79                            t.done = true;
80                            let _ = apply_one_function_value(interp, t.callback.clone(), data_val);
81                        }
82                    }
83                }
84                ProcessMessage::Err(id, err_str) => {
85                    if let Some(t) = self.tasks.get_mut(&id) {
86                        if !t.done {
87                            t.done = true;
88                            let mut map = IndexMap::new();
89                            map.insert("error".to_string(), Value::SingleString(err_str));
90                            let final_val = Value::KeyedArray(map);
91                            let _ = apply_one_function_value(interp, t.callback.clone(), final_val);
92                        }
93                    }
94                }
95            }
96        }
97
98        let before = self.tasks.len();
99        self.tasks.retain(|_, t| !t.done);
100        let removed = before.saturating_sub(self.tasks.len());
101        if removed > 0 {
102            self.active_count.fetch_sub(removed, Ordering::SeqCst);
103        }
104    }
105
106    fn count_tasks(&self) -> usize {
107        self.active_count.load(Ordering::SeqCst)
108    }
109}
110
111// Not currently called, so mark it unused to avoid warnings
112#[allow(dead_code)]
113fn process_spawn_bridge(
114    _interp: &mut Interpreter,
115    _args: Vec<Value>
116) -> Result<Value, String> {
117    // If you eventually need to spawn tasks, this is where you'd do it
118    Ok(Value::Bool(true))
119}
120
121/// process:info => gather some system data in a callback
122fn process_info_bridge(
123    interp: &mut Interpreter,
124    mut args: Vec<Value>
125) -> Result<Value, String> {
126    if args.len() != 1 {
127        return Err(format!("process:info => expected 1 argument => callback, got {}", args.len()));
128    }
129    let callback_val = args.remove(0);
130    let callback_func = match callback_val {
131        Value::Function(fb) => fb,
132        other => return Err(format!("process:info => first arg must be function, got {:?}", other)),
133    };
134
135    // Suppose we gather info synchronously here:
136    let mut map = IndexMap::new();
137    let pid_u32 = std::process::id();
138    map.insert("pid".to_string(), Value::Int(pid_u32 as i32));
139    map.insert("uid".to_string(), Value::Int(get_uid()));
140    map.insert("username".to_string(), Value::SingleString(whoami::username()));
141    map.insert("binary_name".to_string(), Value::SingleString("mumu".into()));
142    map.insert("event_loop_len".to_string(), Value::Int(42));
143
144    let info_val = Value::KeyedArray(map);
145
146    // Call the user’s callback => callback(info_val)
147    let _ = apply_one_function_value(interp, callback_func, info_val)?;
148
149    Ok(Value::Bool(true))
150}
151
152/// process:check_tasks => poll manager, return how many tasks remain
153fn process_check_tasks_bridge(
154    interp: &mut Interpreter,
155    _args: Vec<Value>
156) -> Result<Value, String> {
157    let mut mgr = PROCESS_MANAGER.lock().unwrap();
158    mgr.poll_events(interp);
159    let count = mgr.count_tasks();
160    Ok(Value::Int(count as i32))
161}
162
163lazy_static! {
164    static ref PROCESS_MANAGER: Mutex<ProcessManager> = Mutex::new(ProcessManager::new());
165}
166
167#[export_name = "Cargo_lock"]
168pub unsafe extern "C" fn cargo_lock(
169    interp_ptr: *mut c_void,
170    _extra_str: *const c_void,
171) -> i32 {
172    if interp_ptr.is_null() {
173        return 1;
174    }
175    let interp_ref = &mut *(interp_ptr as *mut Interpreter);
176
177    // Register process:info bridging:
178    let info_fn = Arc::new(Mutex::new(process_info_bridge));
179    interp_ref.register_dynamic_function("process:info", info_fn);
180    interp_ref.set_variable(
181        "process:info",
182        Value::Function(Box::new(FunctionValue::Named("process:info".to_string())))
183    );
184
185    // Register process:check_tasks bridging:
186    let check_fn = Arc::new(Mutex::new(process_check_tasks_bridge));
187    interp_ref.register_dynamic_function("process:check_tasks", check_fn);
188    interp_ref.set_variable(
189        "process:check_tasks",
190        Value::Function(Box::new(FunctionValue::Named("process:check_tasks".to_string())))
191    );
192
193    // If you wanted process:spawn bridging, you’d do similarly, e.g.:
194    // let spawn_fn = Arc::new(Mutex::new(process_spawn_bridge));
195    // interp_ref.register_dynamic_function("process:spawn", spawn_fn);
196    // interp_ref.set_variable(
197    //     "process:spawn",
198    //     Value::Function(Box::new(FunctionValue::Named("process:spawn".to_string())))
199    // );
200
201    // Add poller for background tasks if you have any
202    let poller = Arc::new(Mutex::new(move |interp: &mut Interpreter| {
203        let mut mgr = PROCESS_MANAGER.lock().unwrap();
204        mgr.poll_events(interp);
205        mgr.count_tasks()
206    }));
207    interp_ref.add_poller(poller);
208
209    0
210}