Skip to main content

maa_framework/
context.rs

1//! Task execution context for pipeline runtime operations.
2
3use std::ffi::CString;
4use std::ptr::NonNull;
5
6use crate::{common, sys, MaaError, MaaResult};
7
8/// Represents the execution context of a task.
9///
10/// This struct provides an interface for interacting with the current task's runtime state.
11/// Capabilities include:
12/// - Executing sub-tasks.
13/// - Overriding pipeline configurations dynamically.
14/// - Performing direct recognition and actions.
15/// - Managing node hit counts and control flow anchors.
16///
17/// # Safety
18///
19/// `Context` is a wrapper around a non-owning pointer (`MaaContext`).
20/// The underlying resources are managed by the `Tasker`. Users must ensure the `Context`
21/// does not outlive the validity of the underlying task or callback scope.
22pub struct Context {
23    handle: NonNull<sys::MaaContext>,
24}
25
26unsafe impl Send for Context {}
27
28impl std::fmt::Debug for Context {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("Context")
31            .field("handle", &self.handle)
32            .finish()
33    }
34}
35
36impl Context {
37    pub(crate) unsafe fn from_raw(ptr: *mut sys::MaaContext) -> Option<Self> {
38        NonNull::new(ptr).map(|handle| Self { handle })
39    }
40
41    /// Submits a new task for execution.
42    ///
43    /// # Arguments
44    ///
45    /// * `entry` - The name of the task entry point.
46    /// * `pipeline_override` - A JSON string specifying pipeline parameter overrides.
47    ///
48    /// # Returns
49    ///
50    /// Returns the job ID (`MaaId`) associated with the submitted task.
51    pub fn run_task(&self, entry: &str, pipeline_override: &str) -> MaaResult<i64> {
52        let c_entry = CString::new(entry)?;
53        let c_pipeline = CString::new(pipeline_override)?;
54        let id = unsafe {
55            sys::MaaContextRunTask(self.handle.as_ptr(), c_entry.as_ptr(), c_pipeline.as_ptr())
56        };
57        Ok(id)
58    }
59
60    /// Overrides pipeline parameters for the current context.
61    ///
62    /// # Arguments
63    ///
64    /// * `override_json` - A JSON string containing the parameters to override.
65    pub fn override_pipeline(&self, override_json: &str) -> MaaResult<()> {
66        let c_json = CString::new(override_json)?;
67        let ret = unsafe { sys::MaaContextOverridePipeline(self.handle.as_ptr(), c_json.as_ptr()) };
68        common::check_bool(ret)
69    }
70
71    /// Returns the underlying raw `MaaContext` pointer.
72    ///
73    /// Useful for advanced FFI interop scenarios.
74    #[inline]
75    pub fn raw(&self) -> *mut sys::MaaContext {
76        self.handle.as_ptr()
77    }
78
79    /// Runs a specific recognition task with an input image.
80    ///
81    /// # Arguments
82    ///
83    /// * `entry` - The task entry name.
84    /// * `pipeline_override` - A JSON string for parameter overrides.
85    /// * `image` - The input image buffer.
86    ///
87    /// # Returns
88    ///
89    /// Returns the job ID associated with the recognition task.
90    pub fn run_recognition(
91        &self,
92        entry: &str,
93        pipeline_override: &str,
94        image: &crate::buffer::MaaImageBuffer,
95    ) -> MaaResult<i64> {
96        let c_entry = CString::new(entry)?;
97        let c_pipeline = CString::new(pipeline_override)?;
98        let id = unsafe {
99            sys::MaaContextRunRecognition(
100                self.handle.as_ptr(),
101                c_entry.as_ptr(),
102                c_pipeline.as_ptr(),
103                image.as_ptr(),
104            )
105        };
106        Ok(id)
107    }
108
109    /// Performs a direct recognition operation.
110    ///
111    /// This executes a specific recognition algorithm immediately, bypassing the pipeline structure.
112    ///
113    /// # Arguments
114    ///
115    /// * `reco_type` - The specific recognition algorithm type (e.g., "TemplateMatch", "OCR").
116    /// * `reco_param` - A JSON string containing the recognition parameters.
117    /// * `image` - The image buffer to perform recognition on.
118    ///
119    /// # Returns
120    ///
121    /// Returns `Some(RecognitionDetail)` if successful, or `None` if the operation failed
122    /// to initiate or yielded no result.
123    pub fn run_recognition_direct(
124        &self,
125        reco_type: &str,
126        reco_param: &str,
127        image: &crate::buffer::MaaImageBuffer,
128    ) -> MaaResult<Option<crate::common::RecognitionDetail>> {
129        let c_type = CString::new(reco_type)?;
130        let c_param = CString::new(reco_param)?;
131        let id = unsafe {
132            sys::MaaContextRunRecognitionDirect(
133                self.handle.as_ptr(),
134                c_type.as_ptr(),
135                c_param.as_ptr(),
136                image.as_ptr(),
137            )
138        };
139
140        if id == 0 {
141            return Ok(None);
142        }
143
144        let tasker_ptr = self.tasker_handle();
145        crate::tasker::Tasker::fetch_recognition_detail(tasker_ptr, id)
146    }
147
148    /// Runs a specific action with context parameters.
149    ///
150    /// # Arguments
151    ///
152    /// * `entry` - The task entry name.
153    /// * `pipeline_override` - A JSON string for parameter overrides.
154    /// * `box_rect` - The target region for the action.
155    /// * `reco_detail` - A string containing details from a previous recognition step.
156    ///
157    /// # Returns
158    ///
159    /// Returns the job ID associated with the action.
160    pub fn run_action(
161        &self,
162        entry: &str,
163        pipeline_override: &str,
164        box_rect: &common::Rect,
165        reco_detail: &str,
166    ) -> MaaResult<i64> {
167        let c_entry = CString::new(entry)?;
168        let c_pipeline = CString::new(pipeline_override)?;
169        let c_detail = CString::new(reco_detail)?;
170        let maa_rect = sys::MaaRect {
171            x: box_rect.x,
172            y: box_rect.y,
173            width: box_rect.width,
174            height: box_rect.height,
175        };
176        let id = unsafe {
177            sys::MaaContextRunAction(
178                self.handle.as_ptr(),
179                c_entry.as_ptr(),
180                c_pipeline.as_ptr(),
181                &maa_rect,
182                c_detail.as_ptr(),
183            )
184        };
185        Ok(id)
186    }
187
188    /// Performs a direct action operation.
189    ///
190    /// This executes a specific action immediately, bypassing the pipeline structure.
191    ///
192    /// # Arguments
193    ///
194    /// * `action_type` - The type of action to perform (e.g., "Click", "Swipe").
195    /// * `action_param` - A JSON string containing the action parameters.
196    /// * `box_rect` - The target region for the action (e.g., derived from recognition results).
197    /// * `reco_detail` - Contextual details from a previous recognition step.
198    ///
199    /// # Returns
200    ///
201    /// Returns `Some(ActionDetail)` on success, or `None` if the operation failed.
202    pub fn run_action_direct(
203        &self,
204        action_type: &str,
205        action_param: &str,
206        box_rect: &common::Rect,
207        reco_detail: &str,
208    ) -> MaaResult<Option<crate::common::ActionDetail>> {
209        let c_type = CString::new(action_type)?;
210        let c_param = CString::new(action_param)?;
211        let c_detail = CString::new(reco_detail)?;
212        let maa_rect = sys::MaaRect {
213            x: box_rect.x,
214            y: box_rect.y,
215            width: box_rect.width,
216            height: box_rect.height,
217        };
218
219        let id = unsafe {
220            sys::MaaContextRunActionDirect(
221                self.handle.as_ptr(),
222                c_type.as_ptr(),
223                c_param.as_ptr(),
224                &maa_rect,
225                c_detail.as_ptr(),
226            )
227        };
228
229        if id == 0 {
230            return Ok(None);
231        }
232
233        let tasker_ptr = self.tasker_handle();
234        crate::tasker::Tasker::fetch_action_detail(tasker_ptr, id)
235    }
236
237    /// Overrides the execution list for a specific node.
238    ///
239    /// # Arguments
240    ///
241    /// * `node_name` - The name of the target node.
242    /// * `next_list` - A slice of strings representing the new next list.
243    ///   Supports special signals like `[JumpBack]` and `[Anchor]`.
244    ///
245    /// # Returns
246    ///
247    /// * `Ok(true)` if the override was successful.
248    /// * `Ok(false)` if the operation failed.
249    pub fn override_next(&self, node_name: &str, next_list: &[&str]) -> MaaResult<bool> {
250        let c_name = CString::new(node_name)?;
251        let list_buf = crate::buffer::MaaStringListBuffer::new()?;
252        list_buf.set(next_list)?;
253
254        let ret = unsafe {
255            sys::MaaContextOverrideNext(self.handle.as_ptr(), c_name.as_ptr(), list_buf.as_ptr())
256        };
257        Ok(ret != 0)
258    }
259
260    /// Retrieves data associated with a specific node.
261    ///
262    /// # Arguments
263    ///
264    /// * `node_name` - The name of the node.
265    ///
266    /// # Returns
267    ///
268    /// Returns the node data as a `String` if available, or `None` otherwise.
269    pub fn get_node_data(&self, node_name: &str) -> MaaResult<Option<String>> {
270        let c_name = CString::new(node_name)?;
271        let buffer = crate::buffer::MaaStringBuffer::new()?;
272        let ret = unsafe {
273            sys::MaaContextGetNodeData(self.handle.as_ptr(), c_name.as_ptr(), buffer.as_ptr())
274        };
275        if ret != 0 {
276            Ok(Some(buffer.to_string()))
277        } else {
278            Ok(None)
279        }
280    }
281
282    /// Retrieves and deserializes the object associated with a node.
283    ///
284    /// This is a convenience wrapper around `get_node_data` that parses the result into `PipelineData`.
285    ///
286    /// # Arguments
287    ///
288    /// * `node_name` - The name of the node.
289    ///
290    /// # Errors
291    ///
292    /// Returns `MaaError::InvalidConfig` if the data cannot be parsed.
293    pub fn get_node_object(
294        &self,
295        node_name: &str,
296    ) -> MaaResult<Option<crate::pipeline::PipelineData>> {
297        if let Some(json_str) = self.get_node_data(node_name)? {
298            let data: crate::pipeline::PipelineData =
299                serde_json::from_str(&json_str).map_err(|e| {
300                    MaaError::InvalidConfig(format!("Failed to parse pipeline data: {}", e))
301                })?;
302            Ok(Some(data))
303        } else {
304            Ok(None)
305        }
306    }
307
308    /// Returns the ID of the current task.
309    pub fn task_id(&self) -> common::MaaId {
310        unsafe { sys::MaaContextGetTaskId(self.handle.as_ptr()) }
311    }
312
313    /// Associates an anchor with a specific node.
314    ///
315    /// # Arguments
316    ///
317    /// * `anchor_name` - The name of the anchor.
318    /// * `node_name` - The name of the target node.
319    pub fn set_anchor(&self, anchor_name: &str, node_name: &str) -> MaaResult<()> {
320        let c_anchor = CString::new(anchor_name)?;
321        let c_node = CString::new(node_name)?;
322        let ret = unsafe {
323            sys::MaaContextSetAnchor(self.handle.as_ptr(), c_anchor.as_ptr(), c_node.as_ptr())
324        };
325        common::check_bool(ret)
326    }
327
328    /// Retrieves the node name associated with an anchor.
329    ///
330    /// # Arguments
331    ///
332    /// * `anchor_name` - The name of the anchor.
333    ///
334    /// # Returns
335    ///
336    /// Returns the node name as a `String` if the anchor exists.
337    pub fn get_anchor(&self, anchor_name: &str) -> MaaResult<Option<String>> {
338        let c_anchor = CString::new(anchor_name)?;
339        let buffer = crate::buffer::MaaStringBuffer::new()?;
340        let ret = unsafe {
341            sys::MaaContextGetAnchor(self.handle.as_ptr(), c_anchor.as_ptr(), buffer.as_ptr())
342        };
343        if ret != 0 {
344            Ok(Some(buffer.to_string()))
345        } else {
346            Ok(None)
347        }
348    }
349
350    /// Retrieves the hit count for a specific node.
351    ///
352    /// Hit count tracks how many times a node has been executed.
353    ///
354    /// # Arguments
355    ///
356    /// * `node_name` - The name of the node to query.
357    ///
358    /// # Returns
359    ///
360    /// The number of times the node has been executed, or 0 if not found.
361    pub fn get_hit_count(&self, node_name: &str) -> MaaResult<u64> {
362        let c_name = CString::new(node_name)?;
363        let mut count: u64 = 0;
364        let ret = unsafe {
365            sys::MaaContextGetHitCount(self.handle.as_ptr(), c_name.as_ptr(), &mut count)
366        };
367        if ret != 0 {
368            Ok(count)
369        } else {
370            Ok(0)
371        }
372    }
373
374    /// Resets the hit count for a specific node to zero.
375    ///
376    /// # Arguments
377    ///
378    /// * `node_name` - The name of the node to reset.
379    pub fn clear_hit_count(&self, node_name: &str) -> MaaResult<()> {
380        let c_name = CString::new(node_name)?;
381        let ret = unsafe { sys::MaaContextClearHitCount(self.handle.as_ptr(), c_name.as_ptr()) };
382        common::check_bool(ret)
383    }
384
385    /// Creates a clone of the current context.
386    ///
387    /// The new context can be used for independent execution threads, preventing
388    /// state interference with the original context.
389    pub fn clone_context(&self) -> MaaResult<Self> {
390        let cloned = unsafe { sys::MaaContextClone(self.handle.as_ptr()) };
391        NonNull::new(cloned)
392            .map(|handle| Self { handle })
393            .ok_or(crate::MaaError::NullPointer)
394    }
395
396    /// Returns the raw handle to the associated `Tasker` instance.
397    ///
398    /// # Safety
399    ///
400    /// The returned pointer is owned by the framework. The caller must not
401    /// attempt to destroy or free it.
402    pub fn tasker_handle(&self) -> *mut sys::MaaTasker {
403        unsafe { sys::MaaContextGetTasker(self.handle.as_ptr()) }
404    }
405
406    /// Overrides a global image resource with a provided buffer.
407    ///
408    /// # Arguments
409    ///
410    /// * `image_name` - The identifier of the image to override.
411    /// * `image` - The new image data buffer.
412    pub fn override_image(
413        &self,
414        image_name: &str,
415        image: &crate::buffer::MaaImageBuffer,
416    ) -> MaaResult<()> {
417        let c_name = CString::new(image_name)?;
418        let ret = unsafe {
419            sys::MaaContextOverrideImage(self.handle.as_ptr(), c_name.as_ptr(), image.as_ptr())
420        };
421        common::check_bool(ret)
422    }
423
424    /// Retrieves a job handle representing the current task's execution state.
425    ///
426    /// The returned job is bound to the lifetime of the `Context` to ensure safety.
427    pub fn get_task_job<'a>(&'a self) -> crate::job::JobWithResult<'a, common::TaskDetail> {
428        let task_id = self.task_id();
429        let tasker_raw = self.tasker_handle() as usize;
430
431        let status_fn: crate::job::StatusFn = Box::new(move |job_id| {
432            let ptr = tasker_raw as *mut sys::MaaTasker;
433            common::MaaStatus(unsafe { sys::MaaTaskerStatus(ptr, job_id) })
434        });
435
436        let wait_fn: crate::job::WaitFn = Box::new(move |job_id| {
437            let ptr = tasker_raw as *mut sys::MaaTasker;
438            common::MaaStatus(unsafe { sys::MaaTaskerWait(ptr, job_id) })
439        });
440
441        let get_fn = move |tid: common::MaaId| -> MaaResult<Option<common::TaskDetail>> {
442            let ptr = tasker_raw as *mut sys::MaaTasker;
443            crate::tasker::Tasker::fetch_task_detail(ptr, tid)
444        };
445
446        crate::job::JobWithResult::new(task_id, status_fn, wait_fn, get_fn)
447    }
448
449    /// Resets hit counts for all nodes in the context.
450    ///
451    /// This clears the execution history for all nodes, resetting their hit counts to zero.
452    pub fn clear_all_hit_counts(&self) -> MaaResult<()> {
453        let ret = unsafe { sys::MaaContextClearHitCount(self.handle.as_ptr(), std::ptr::null()) };
454        common::check_bool(ret)
455    }
456}