Skip to main content

lmflow/ffi/
context.rs

1//! C ABI:Context —— 算子回调期的读输入 / 发输出 / options / side packet
2//!
3//! 由 [`super`](../index.html) 的分节拆出 —— 见 `ffi/mod.rs` 的模块头注释,
4//! 那里定义了整层的约定(catch_unwind 包裹、last_error、空指针检查、布局钉死)。
5#![allow(clippy::missing_safety_doc)]
6
7use std::ffi::{c_char, c_void};
8
9use crate::runtime::last_error;
10use crate::status::{code, Error};
11use crate::timestamp::Timestamp;
12
13use super::*;
14
15// ---------------------------------------------------------------- Context
16
17#[no_mangle]
18pub unsafe extern "C" fn lmflow_ctx_num_inputs(c: *const LMFlowContext) -> usize {
19    guard_val(0, || ctx_ref(c).map_or(0, |x| x.in_ports.len()))
20}
21#[no_mangle]
22pub unsafe extern "C" fn lmflow_ctx_num_outputs(c: *const LMFlowContext) -> usize {
23    guard_val(0, || ctx_ref(c).map_or(0, |x| x.out_ports.len()))
24}
25#[no_mangle]
26pub unsafe extern "C" fn lmflow_ctx_input_id(
27    c: *const LMFlowContext,
28    tag: *const c_char,
29    index: usize,
30) -> usize {
31    guard_val(INVALID_ID, || {
32        ctx_ref(c)
33            .and_then(|x| x.in_ports.id_by_tag(cstr(tag).unwrap_or(""), index))
34            .unwrap_or(INVALID_ID)
35    })
36}
37#[no_mangle]
38pub unsafe extern "C" fn lmflow_ctx_output_id(
39    c: *const LMFlowContext,
40    tag: *const c_char,
41    index: usize,
42) -> usize {
43    guard_val(INVALID_ID, || {
44        ctx_ref(c)
45            .and_then(|x| x.out_ports.id_by_tag(cstr(tag).unwrap_or(""), index))
46            .unwrap_or(INVALID_ID)
47    })
48}
49#[no_mangle]
50pub unsafe extern "C" fn lmflow_ctx_input_index(
51    c: *const LMFlowContext,
52    name: *const c_char,
53) -> usize {
54    guard_val(INVALID_ID, || {
55        ctx_ref(c)
56            .and_then(|x| x.in_ports.index_by_name(cstr(name).unwrap_or("")))
57            .unwrap_or(INVALID_ID)
58    })
59}
60#[no_mangle]
61pub unsafe extern "C" fn lmflow_ctx_output_index(
62    c: *const LMFlowContext,
63    name: *const c_char,
64) -> usize {
65    guard_val(INVALID_ID, || {
66        ctx_ref(c)
67            .and_then(|x| x.out_ports.index_by_name(cstr(name).unwrap_or("")))
68            .unwrap_or(INVALID_ID)
69    })
70}
71#[no_mangle]
72pub unsafe extern "C" fn lmflow_ctx_input_name(
73    c: *const LMFlowContext,
74    idx: usize,
75) -> *const c_char {
76    guard_val(c"".as_ptr(), || {
77        ctx_ref(c)
78            .and_then(|x| x.in_ports.name(idx).map(|s| x.intern(s)))
79            .unwrap_or(c"".as_ptr())
80    })
81}
82#[no_mangle]
83pub unsafe extern "C" fn lmflow_ctx_output_name(
84    c: *const LMFlowContext,
85    idx: usize,
86) -> *const c_char {
87    guard_val(c"".as_ptr(), || {
88        ctx_ref(c)
89            .and_then(|x| x.out_ports.name(idx).map(|s| x.intern(s)))
90            .unwrap_or(c"".as_ptr())
91    })
92}
93#[no_mangle]
94pub unsafe extern "C" fn lmflow_ctx_node_name(c: *const LMFlowContext) -> *const c_char {
95    guard_val(c"".as_ptr(), || {
96        ctx_ref(c).map_or(c"".as_ptr(), |x| x.node_name_cstr())
97    })
98}
99#[no_mangle]
100pub unsafe extern "C" fn lmflow_ctx_kernel_name(c: *const LMFlowContext) -> *const c_char {
101    guard_val(c"".as_ptr(), || {
102        ctx_ref(c).map_or(c"".as_ptr(), |x| x.kernel_name_cstr())
103    })
104}
105#[no_mangle]
106pub unsafe extern "C" fn lmflow_ctx_log(c: *const LMFlowContext, level: i32, msg: *const c_char) {
107    guard_val((), || {
108        if let (Some(x), Some(m)) = (ctx_ref(c), cstr(msg)) {
109            x.log(level, m);
110        }
111    });
112}
113#[no_mangle]
114pub unsafe extern "C" fn lmflow_ctx_set_error(c: *const LMFlowContext, msg: *const c_char) {
115    guard_val((), || {
116        // set_error 需要可变访问;上下文在回调期间由算子独占,故此转换安全
117        if let (Some(x), Some(m)) = (ctx_mut(c as *mut LMFlowContext), cstr(msg)) {
118            x.set_error(m);
119        }
120    });
121}
122#[no_mangle]
123pub unsafe extern "C" fn lmflow_ctx_source_done(c: *const LMFlowContext) {
124    guard_val((), || {
125        // 源算子自报「已产完」;上下文在回调期间由算子独占,可变转换安全。
126        if let Some(x) = ctx_mut(c as *mut LMFlowContext) {
127            x.source_done = true;
128        }
129    });
130}
131#[no_mangle]
132pub unsafe extern "C" fn lmflow_ctx_source_yield(c: *const LMFlowContext, delay_ms: u64) {
133    guard_val((), || {
134        if let Some(x) = ctx_mut(c as *mut LMFlowContext) {
135            x.source_yield = Some(std::time::Duration::from_millis(delay_ms));
136        }
137    });
138}
139#[no_mangle]
140pub unsafe extern "C" fn lmflow_ctx_close_reason(c: *const LMFlowContext) -> i32 {
141    guard_val(0, || ctx_ref(c).map_or(0, |x| x.close_reason))
142}
143#[no_mangle]
144pub unsafe extern "C" fn lmflow_ctx_counter_add(
145    c: *const LMFlowContext,
146    name: *const c_char,
147    delta: i64,
148) {
149    guard_val((), || {
150        if let (Some(x), Some(n)) = (ctx_ref(c), cstr(name)) {
151            x.shared.counter_add(n, delta);
152        }
153    });
154}
155
156#[no_mangle]
157pub unsafe extern "C" fn lmflow_ctx_input_is_empty(c: *const LMFlowContext, idx: usize) -> bool {
158    guard_val(true, || {
159        ctx_ref(c).is_none_or(|x| x.input(idx).is_none_or(|p| p.is_empty()))
160    })
161}
162#[no_mangle]
163pub unsafe extern "C" fn lmflow_ctx_input_is_done(c: *const LMFlowContext, idx: usize) -> bool {
164    guard_val(false, || {
165        ctx_ref(c).is_some_and(|x| x.inputs_done.get(idx).copied().unwrap_or(false))
166    })
167}
168#[no_mangle]
169pub unsafe extern "C" fn lmflow_ctx_input(c: *const LMFlowContext, idx: usize) -> LMFlowPacket {
170    guard_val(LMFlowPacket::default(), || match ctx_ref(c) {
171        Some(x) => x.input(idx).map(borrow_packet).unwrap_or_default(),
172        None => LMFlowPacket::default(),
173    })
174}
175#[no_mangle]
176pub unsafe extern "C" fn lmflow_ctx_input_count(c: *const LMFlowContext, idx: usize) -> usize {
177    // 本次调用某输入口的包数:单包策略恒 0/1;batch 策略为该批实际大小。
178    guard_val(0, || ctx_ref(c).map_or(0, |x| x.input_count(idx)))
179}
180#[no_mangle]
181pub unsafe extern "C" fn lmflow_ctx_input_at(
182    c: *const LMFlowContext,
183    idx: usize,
184    k: usize,
185) -> LMFlowPacket {
186    // 借用第 k 个输入包(语义同 lmflow_ctx_input,不转移引用计数)。batch 策略下遍历一批。
187    guard_val(LMFlowPacket::default(), || match ctx_ref(c) {
188        Some(x) => x.input_at(idx, k).map(borrow_packet).unwrap_or_default(),
189        None => LMFlowPacket::default(),
190    })
191}
192#[no_mangle]
193pub unsafe extern "C" fn lmflow_ctx_input_payload(
194    c: *const LMFlowContext,
195    idx: usize,
196) -> *const c_void {
197    guard_val(std::ptr::null(), || {
198        ctx_ref(c)
199            .and_then(|x| x.input(idx))
200            .and_then(|p| p.arc_ref().map(|a| a.payload.data_ptr() as *const c_void))
201            .unwrap_or(std::ptr::null())
202    })
203}
204#[no_mangle]
205pub unsafe extern "C" fn lmflow_ctx_input_timestamp(c: *const LMFlowContext) -> i64 {
206    guard_val(Timestamp::unset().0, || {
207        ctx_ref(c).map_or(Timestamp::unset().0, |x| x.input_ts.0)
208    })
209}
210#[no_mangle]
211pub unsafe extern "C" fn lmflow_ctx_take_input(c: *mut LMFlowContext, idx: usize) -> LMFlowPacket {
212    guard_val(LMFlowPacket::default(), || match ctx_mut(c) {
213        Some(x) => own_packet(x.take_input(idx)),
214        None => LMFlowPacket::default(),
215    })
216}
217#[no_mangle]
218pub unsafe extern "C" fn lmflow_ctx_emit(c: *mut LMFlowContext, out_idx: usize, pkt: LMFlowPacket) {
219    guard_val((), || {
220        let p = take_packet(pkt);
221        if let Some(x) = ctx_mut(c) {
222            if let Err(e) = x.emit(out_idx, p) {
223                if !x.output_failed {
224                    x.set_error(&e.to_string());
225                }
226                x.output_failed = true;
227                last_error::set(&e.to_string());
228            }
229        }
230    });
231}
232#[no_mangle]
233pub unsafe extern "C" fn lmflow_ctx_forward(c: *mut LMFlowContext, in_idx: usize, out_idx: usize) {
234    guard_val((), || {
235        if let Some(x) = ctx_mut(c) {
236            if let Err(e) = x.forward(in_idx, out_idx) {
237                if !x.output_failed {
238                    x.set_error(&e.to_string());
239                }
240                x.output_failed = true;
241                last_error::set(&e.to_string());
242            }
243        }
244    });
245}
246#[no_mangle]
247pub unsafe extern "C" fn lmflow_ctx_set_next_ts_bound(
248    c: *mut LMFlowContext,
249    out_idx: usize,
250    bound: i64,
251) {
252    guard_val((), || {
253        if let Some(x) = ctx_mut(c) {
254            x.set_next_bound(out_idx, Timestamp(bound));
255        }
256    });
257}
258
259// ---- options ----
260
261macro_rules! opt_scalar {
262    ($name:ident, $method:ident, $ty:ty) => {
263        #[no_mangle]
264        pub unsafe extern "C" fn $name(
265            c: *const LMFlowContext,
266            key: *const c_char,
267            def: $ty,
268        ) -> $ty {
269            guard_val(def, || {
270                ctx_ref(c)
271                    .and_then(|x| cstr(key).and_then(|k| x.options.$method(k)))
272                    .unwrap_or(def)
273            })
274        }
275    };
276}
277opt_scalar!(lmflow_ctx_option_i64, i64, i64);
278opt_scalar!(lmflow_ctx_option_f64, f64, f64);
279opt_scalar!(lmflow_ctx_option_bool, bool, bool);
280
281#[no_mangle]
282pub unsafe extern "C" fn lmflow_ctx_has_option(
283    c: *const LMFlowContext,
284    key: *const c_char,
285) -> bool {
286    guard_val(false, || {
287        ctx_ref(c).is_some_and(|x| cstr(key).is_some_and(|k| x.options.has(k)))
288    })
289}
290
291#[no_mangle]
292pub unsafe extern "C" fn lmflow_ctx_option_str(
293    c: *const LMFlowContext,
294    key: *const c_char,
295    def: *const c_char,
296) -> *const c_char {
297    guard_val(def, || {
298        ctx_ref(c)
299            .and_then(|x| cstr(key).and_then(|k| x.options.str_cstr(k)))
300            .unwrap_or(def)
301    })
302}
303
304macro_rules! require_scalar {
305    ($name:ident, $method:ident, $ty:ty) => {
306        #[no_mangle]
307        pub unsafe extern "C" fn $name(
308            c: *const LMFlowContext,
309            key: *const c_char,
310            out: *mut $ty,
311        ) -> i32 {
312            guard(|| {
313                let Some(x) = ctx_ref(c) else {
314                    return fail(Error::InvalidArg("context is null".into()));
315                };
316                let Some(k) = cstr(key) else {
317                    return fail(Error::InvalidArg("parameter name is null".into()));
318                };
319                match x.options.$method(k) {
320                    Some(v) => {
321                        if !out.is_null() {
322                            *out = v;
323                        }
324                        code::OK
325                    }
326                    None => fail(Error::InvalidArg(format!(
327                        "node `{}`: required parameter options.{k} is missing or type mismatch",
328                        x.node_name
329                    ))),
330                }
331            })
332        }
333    };
334}
335require_scalar!(lmflow_ctx_require_option_i64, i64, i64);
336require_scalar!(lmflow_ctx_require_option_f64, f64, f64);
337require_scalar!(lmflow_ctx_require_option_bool, bool, bool);
338
339#[no_mangle]
340pub unsafe extern "C" fn lmflow_ctx_require_option_str(
341    c: *const LMFlowContext,
342    key: *const c_char,
343    out: *mut *const c_char,
344) -> i32 {
345    guard(|| {
346        let Some(x) = ctx_ref(c) else {
347            return fail(Error::InvalidArg("context is null".into()));
348        };
349        let Some(k) = cstr(key) else {
350            return fail(Error::InvalidArg("parameter name is null".into()));
351        };
352        match x.options.str_cstr(k) {
353            Some(p) => {
354                if !out.is_null() {
355                    *out = p;
356                }
357                code::OK
358            }
359            None => fail(Error::InvalidArg(format!(
360                "node `{}`: required parameter options.{k} is missing or type mismatch",
361                x.node_name
362            ))),
363        }
364    })
365}
366
367#[no_mangle]
368pub unsafe extern "C" fn lmflow_ctx_option_count(
369    c: *const LMFlowContext,
370    key: *const c_char,
371) -> usize {
372    guard_val(0, || {
373        ctx_ref(c).map_or(0, |x| cstr(key).map_or(0, |k| x.options.count(k)))
374    })
375}
376
377#[no_mangle]
378pub unsafe extern "C" fn lmflow_ctx_option_i64_array(
379    c: *const LMFlowContext,
380    key: *const c_char,
381    out: *mut i64,
382    cap: usize,
383) -> usize {
384    guard_val(0, || {
385        let (Some(x), Some(k)) = (ctx_ref(c), cstr(key)) else {
386            return 0;
387        };
388        let mut tmp = vec![0i64; cap];
389        let n = x.options.i64_array(k, &mut tmp);
390        if !out.is_null() && cap > 0 {
391            std::ptr::copy_nonoverlapping(tmp.as_ptr(), out, cap.min(n));
392        }
393        n
394    })
395}
396
397#[no_mangle]
398pub unsafe extern "C" fn lmflow_ctx_option_f64_array(
399    c: *const LMFlowContext,
400    key: *const c_char,
401    out: *mut f64,
402    cap: usize,
403) -> usize {
404    guard_val(0, || {
405        let (Some(x), Some(k)) = (ctx_ref(c), cstr(key)) else {
406            return 0;
407        };
408        let mut tmp = vec![0f64; cap];
409        let n = x.options.f64_array(k, &mut tmp);
410        if !out.is_null() && cap > 0 {
411            std::ptr::copy_nonoverlapping(tmp.as_ptr(), out, cap.min(n));
412        }
413        n
414    })
415}
416
417#[no_mangle]
418pub unsafe extern "C" fn lmflow_ctx_option_str_array(
419    c: *const LMFlowContext,
420    key: *const c_char,
421    out: *mut *const c_char,
422    cap: usize,
423) -> usize {
424    guard_val(0, || {
425        let (Some(x), Some(k)) = (ctx_ref(c), cstr(key)) else {
426            return 0;
427        };
428        let mut tmp = vec![std::ptr::null(); cap];
429        let n = x.options.str_array(k, &mut tmp);
430        if !out.is_null() && cap > 0 {
431            std::ptr::copy_nonoverlapping(tmp.as_ptr(), out, cap.min(n));
432        }
433        n
434    })
435}
436
437#[no_mangle]
438pub unsafe extern "C" fn lmflow_ctx_options_json(c: *const LMFlowContext) -> *const c_char {
439    guard_val(c"{}".as_ptr(), || {
440        ctx_ref(c).map_or(c"{}".as_ptr(), |x| x.options.json_cstr())
441    })
442}
443
444// ---- side packet ----
445
446#[no_mangle]
447pub unsafe extern "C" fn lmflow_ctx_has_side_packet(
448    c: *const LMFlowContext,
449    name: *const c_char,
450) -> bool {
451    guard_val(false, || {
452        ctx_ref(c).is_some_and(|x| cstr(name).is_some_and(|n| x.side_packets.contains_key(n)))
453    })
454}
455
456#[no_mangle]
457pub unsafe extern "C" fn lmflow_ctx_side_packet(
458    c: *const LMFlowContext,
459    name: *const c_char,
460) -> LMFlowPacket {
461    guard_val(LMFlowPacket::default(), || {
462        ctx_ref(c)
463            .and_then(|x| cstr(name).and_then(|n| x.side_packets.get(n)))
464            .map(borrow_packet)
465            .unwrap_or_default()
466    })
467}