rpi_plugin_sdk/
callbacks.rs1use crate::{StbString, StepResult, StepResultTag};
7
8pub struct Output<'a> {
9 slot: Option<&'a mut StbString>,
10 written: bool,
11}
12
13impl<'a> Output<'a> {
14 pub unsafe fn from_raw(slot: *mut StbString) -> Self {
19 Self {
20 slot: unsafe { slot.as_mut() },
21 written: false,
22 }
23 }
24
25 pub fn is_available(&self) -> bool {
26 self.slot.is_some()
27 }
28
29 pub fn write(&mut self, text: String) -> bool {
32 self.replace(Some(text))
33 }
34
35 pub fn clear(&mut self) -> bool {
36 self.replace(None)
37 }
38
39 fn replace(&mut self, text: Option<String>) -> bool {
40 let Some(slot) = self.slot.as_deref_mut() else {
41 return false;
42 };
43 if self.written {
44 free_string(*slot);
45 }
46 *slot = text
47 .map(StbString::from_string)
48 .unwrap_or_else(StbString::empty);
49 self.written = true;
50 true
51 }
52}
53
54pub extern "C" fn free_string(value: StbString) {
60 if value.is_empty() || value.ptr.is_null() {
61 return;
62 }
63 unsafe {
66 drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(
67 value.ptr as *mut u8,
68 value.len,
69 )));
70 }
71}
72
73pub extern "C" fn free_step_result(result: StepResult) {
76 let text = unsafe {
78 match result.tag {
79 StepResultTag::Pending => result.pending_payload().progress,
80 StepResultTag::Done => result.done_payload().result,
81 StepResultTag::Err => result.err_payload().message,
82 }
83 };
84 free_string(text);
85}
86
87#[derive(Debug, PartialEq, Eq)]
89pub enum Step {
90 Pending(String),
91 Done(String),
92 Err(String),
93}
94
95pub unsafe fn take_step_result(result: StepResult) -> Step {
99 let copy = unsafe {
101 match result.tag {
102 StepResultTag::Pending => {
103 Step::Pending(result.pending_payload().progress.to_string_lossy())
104 }
105 StepResultTag::Done => Step::Done(result.done_payload().result.to_string_lossy()),
106 StepResultTag::Err => Step::Err(result.err_payload().message.to_string_lossy()),
107 }
108 };
109 free_step_result(result);
110 copy
111}
112
113#[macro_export]
116macro_rules! take_step_result {
117 ($result:expr) => {{
118 unsafe { $crate::callbacks::take_step_result($result) }
120 }};
121}
122
123#[macro_export]
127macro_rules! string_handler {
128 ($(#[$attr:meta])* $vis:vis fn $name:ident($input:ident, $out:ident, $data:ident) $body:block) => {
129 $(#[$attr])*
130 $vis extern "C" fn $name(raw: $crate::StbStringRef, slot: *mut $crate::StbString,
131 $data: *mut std::ffi::c_void) -> i32 {
132 $crate::guard_or(1, || {
133 let text = unsafe { raw.as_str() }.to_owned();
136 let $input = text.as_str();
137 let mut output = unsafe { $crate::callbacks::Output::from_raw(slot) };
138 let $out = &mut output;
139 $body
140 })
141 }
142 };
143 ($(#[$attr:meta])* $vis:vis fn $name:ident($action:ident, $input:ident, $out:ident, $data:ident) $body:block) => {
144 $(#[$attr])*
145 $vis extern "C" fn $name($action: u32, raw: $crate::StbStringRef, slot: *mut $crate::StbString,
146 $data: *mut std::ffi::c_void) -> i32 {
147 $crate::guard_or(1, || {
148 let text = unsafe { raw.as_str() }.to_owned();
150 let $input = text.as_str();
151 let mut output = unsafe { $crate::callbacks::Output::from_raw(slot) };
152 let $out = &mut output;
153 $body
154 })
155 }
156 };
157}
158
159#[macro_export]
162macro_rules! execute_handler {
163 (fn $name:ident($id:ident, $params:ident, $free:ident) $body:block) => {
164 extern "C" fn $name(
165 raw: $crate::StbStringRef,
166 $params: $crate::StbString,
167 $free: Option<$crate::FreeStringFn>,
168 ) -> $crate::StepHandle {
169 $crate::guard_or(std::ptr::null_mut(), || {
170 let text = unsafe { raw.as_str() }.to_owned();
172 let $id = text.as_str();
173 $body
174 })
175 }
176 };
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::StbStringRef;
183
184 string_handler! { fn echo(input, output, _data) {
185 i32::from(!output.write(input.to_owned()))
186 } }
187 string_handler! { fn replace_twice(input, output, _data) {
188 output.write(input.to_owned());
189 output.write("second".into());
190 0
191 } }
192 string_handler! { fn panicking(_input, output, _data) {
193 output.write("valid output before panic".into());
194 panic!("callback failed");
195 } }
196 string_handler! { fn action(action, input, output, _data) {
197 output.write(format!("{action}:{input}"));
198 0
199 } }
200
201 #[test]
202 fn missing_output_is_reported_without_a_pointer_write() {
203 assert_eq!(
204 echo(
205 StbStringRef::from_str("text"),
206 std::ptr::null_mut(),
207 std::ptr::null_mut()
208 ),
209 1
210 );
211 }
212
213 #[test]
214 fn strings_are_copied_and_repeated_output_writes_are_reclaimed() {
215 let mut output = StbString::empty();
216 assert_eq!(
217 echo(
218 StbStringRef::from_str("你好"),
219 &mut output,
220 std::ptr::null_mut()
221 ),
222 0
223 );
224 assert_eq!(output.to_string_lossy(), "你好");
225 free_string(output);
226 output = StbString::empty();
227 assert_eq!(
228 replace_twice(
229 StbStringRef::from_str("first"),
230 &mut output,
231 std::ptr::null_mut()
232 ),
233 0
234 );
235 assert_eq!(output.to_string_lossy(), "second");
236 free_string(output);
237 }
238
239 #[test]
240 fn panics_leave_a_valid_reclaimable_output() {
241 let mut output = StbString::empty();
242 assert_eq!(
243 panicking(StbStringRef::empty(), &mut output, std::ptr::null_mut()),
244 1
245 );
246 assert_eq!(output.to_string_lossy(), "valid output before panic");
247 free_string(output);
248 }
249
250 #[test]
251 fn runtime_action_signature_is_preserved() {
252 let mut output = StbString::empty();
253 assert_eq!(
254 action(
255 17,
256 StbStringRef::from_str("{}"),
257 &mut output,
258 std::ptr::null_mut()
259 ),
260 0
261 );
262 assert_eq!(output.to_string_lossy(), "17:{}");
263 free_string(output);
264 }
265
266 #[test]
267 fn writing_does_not_reclaim_an_initial_host_owned_value() {
268 let initial = StbString::from_string("host-owned".into());
269 let mut output = initial;
270 assert_eq!(
271 echo(
272 StbStringRef::from_str("plugin-owned"),
273 &mut output,
274 std::ptr::null_mut()
275 ),
276 0
277 );
278 assert_eq!(initial.to_string_lossy(), "host-owned");
279 assert_eq!(output.to_string_lossy(), "plugin-owned");
280 free_string(initial);
281 free_string(output);
282 }
283
284 #[test]
285 fn every_step_result_variant_can_be_reclaimed() {
286 free_step_result(StepResult::pending(StbString::from_string(
287 "progress".into(),
288 )));
289 free_step_result(StepResult::done(StbString::from_string("done".into())));
290 free_step_result(StepResult::err(StbString::from_string("error".into())));
291 free_string(StbString::empty());
292 assert_eq!(
293 take_step_result!(StepResult::err(StbString::from_string("cancelled".into()))),
294 Step::Err("cancelled".into())
295 );
296 }
297}