hapi_rs/
pdg.rs

1use crate::ffi;
2use crate::ffi::{
3    raw::{PdgEventType, PdgState},
4    PDGEventInfo, PDGWorkItemInfo, PDGWorkItemOutputFile,
5};
6use crate::node::{HoudiniNode, NodeHandle};
7use crate::Result;
8use std::fmt::Formatter;
9use std::ops::ControlFlow;
10
11/// Represents a single work item.
12pub struct PDGWorkItem<'node> {
13    pub id: WorkItemId,
14    pub context_id: i32,
15    pub node: &'node HoudiniNode,
16}
17
18#[derive(Debug, Copy, Clone)]
19#[repr(transparent)]
20pub struct WorkItemId(pub(crate) i32);
21
22impl std::fmt::Debug for PDGWorkItem<'_> {
23    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24        f.debug_struct("PDGWorkItem")
25            .field("id", &self.id)
26            .field("context", &self.context_id)
27            .finish()
28    }
29}
30
31impl From<WorkItemId> for i32 {
32    fn from(value: WorkItemId) -> Self {
33        value.0
34    }
35}
36
37impl From<TopNode> for NodeHandle {
38    fn from(value: TopNode) -> Self {
39        value.node.handle
40    }
41}
42
43impl<'session> PDGWorkItem<'session> {
44    pub fn get_info(&self) -> Result<PDGWorkItemInfo> {
45        ffi::get_workitem_info(&self.node.session, self.context_id, self.id.0).map(PDGWorkItemInfo)
46    }
47    /// Retrieve the results of work, if the work item has any.
48    pub fn get_results(&self) -> Result<Vec<PDGWorkItemOutputFile<'session>>> {
49        match self.get_info()?.output_file_count() {
50            0 => Ok(Vec::new()),
51            count => {
52                let results = ffi::get_workitem_result(
53                    &self.node.session,
54                    self.node.handle,
55                    self.id.0,
56                    count,
57                )?;
58                let results = results
59                    .into_iter()
60                    .map(|inner| PDGWorkItemOutputFile(inner, (&self.node.session).into()))
61                    .collect();
62
63                Ok(results)
64            }
65        }
66    }
67
68    pub fn get_data_length(&self, data_name: &str) -> Result<i32> {
69        let data_name = std::ffi::CString::new(data_name)?;
70        ffi::get_workitem_data_length(&self.node.session, self.node.handle, self.id.0, &data_name)
71    }
72
73    pub fn set_int_data(&self, data_name: &str, data: &[i32]) -> Result<()> {
74        let data_name = std::ffi::CString::new(data_name)?;
75        ffi::set_workitem_int_data(
76            &self.node.session,
77            self.node.handle,
78            self.id.0,
79            &data_name,
80            data,
81        )
82    }
83
84    pub fn get_int_data(&self, data_name: &str) -> Result<Vec<i32>> {
85        let data_name = std::ffi::CString::new(data_name)?;
86        let data_size = ffi::get_workitem_data_length(
87            &self.node.session,
88            self.node.handle,
89            self.id.0,
90            &data_name,
91        )?;
92        let mut buffer = Vec::new();
93        buffer.resize(data_size as usize, 0);
94        ffi::get_workitem_int_data(
95            &self.node.session,
96            self.node.handle,
97            self.id.0,
98            &data_name,
99            buffer.as_mut_slice(),
100        )?;
101        Ok(buffer)
102    }
103
104    pub fn set_float_data(&self, data_name: &str, data: &[f32]) -> Result<()> {
105        let data_name = std::ffi::CString::new(data_name)?;
106        ffi::set_workitem_float_data(
107            &self.node.session,
108            self.node.handle,
109            self.id.0,
110            &data_name,
111            data,
112        )
113    }
114
115    pub fn get_float_data(&self, data_name: &str) -> Result<Vec<f32>> {
116        let data_name = std::ffi::CString::new(data_name)?;
117        let data_size = ffi::get_workitem_data_length(
118            &self.node.session,
119            self.node.handle,
120            self.id.0,
121            &data_name,
122        )?;
123        let mut buffer = Vec::new();
124        buffer.resize(data_size as usize, 0.0);
125        ffi::get_workitem_float_data(
126            &self.node.session,
127            self.node.handle,
128            self.id.0,
129            &data_name,
130            buffer.as_mut_slice(),
131        )?;
132        Ok(buffer)
133    }
134
135    pub fn set_int_attribute(&self, attrib_name: &str, value: &[i32]) -> Result<()> {
136        let attrib_name = std::ffi::CString::new(attrib_name)?;
137        ffi::set_workitem_int_attribute(
138            &self.node.session,
139            self.node.handle,
140            self.id.0,
141            &attrib_name,
142            value,
143        )
144    }
145    pub fn get_int_attribute(&self, attr_name: &str) -> Result<Vec<i32>> {
146        let attr_name = std::ffi::CString::new(attr_name)?;
147        let attr_size = ffi::get_workitem_attribute_size(
148            &self.node.session,
149            self.node.handle,
150            self.id.0,
151            &attr_name,
152        )?;
153        let mut buffer = Vec::new();
154        buffer.resize(attr_size as usize, 0);
155        ffi::get_workitem_int_attribute(
156            &self.node.session,
157            self.node.handle,
158            self.id.0,
159            &attr_name,
160            &mut buffer,
161        )?;
162        Ok(buffer)
163    }
164
165    pub fn set_float_attribute(&self, attrib_name: &str, value: &[f32]) -> Result<()> {
166        let attrib_name = std::ffi::CString::new(attrib_name)?;
167        ffi::set_workitem_float_attribute(
168            &self.node.session,
169            self.node.handle,
170            self.id.0,
171            &attrib_name,
172            value,
173        )
174    }
175
176    pub fn get_float_attribute(&self, attr_name: &str) -> Result<Vec<f32>> {
177        let attr_name = std::ffi::CString::new(attr_name)?;
178        let attr_size = ffi::get_workitem_attribute_size(
179            &self.node.session,
180            self.node.handle,
181            self.id.0,
182            &attr_name,
183        )?;
184        let mut buffer = Vec::new();
185        buffer.resize(attr_size as usize, 0.0);
186        ffi::get_workitem_float_attribute(
187            &self.node.session,
188            self.node.handle,
189            self.id.0,
190            &attr_name,
191            &mut buffer,
192        )?;
193        Ok(buffer)
194    }
195}
196
197#[derive(Debug, Clone)]
198/// A wrapper for [`HoudiniNode`] with methods for cooking PDG.
199pub struct TopNode {
200    pub node: HoudiniNode,
201}
202
203/// A convenient wrapper for a single event generated by PDG.
204#[derive(Debug, Copy, Clone)]
205pub struct CookStep {
206    pub event: PDGEventInfo,
207    pub graph_id: i32,
208    pub graph_name: i32,
209}
210
211// Helper to create a vec of events. No Default impl for it.
212fn create_events() -> Vec<ffi::raw::HAPI_PDG_EventInfo> {
213    const NUM: usize = 32;
214    vec![
215        ffi::raw::HAPI_PDG_EventInfo {
216            nodeId: -1,
217            workItemId: -1,
218            dependencyId: -1,
219            currentState: -1,
220            lastState: -1,
221            eventType: -1,
222            msgSH: -1,
223        };
224        NUM
225    ]
226}
227
228impl TopNode {
229    /// Start cooking a TOP node asynchronously.
230    /// For each generated event, a user closure will be called with a [`CookStep`] argument.
231    ///
232    /// The closure returns [`Result<ControlFlow<bool>>`] which is handled like this:
233    ///
234    /// If its an `Err(_)` - bubble up the error.
235    /// If it's [`ControlFlow::Break(bool)`] then the `bool` is either to cancel the cooking
236    /// or just break the loop and return.
237    /// In case of [`ControlFlow::Continue(_)`] run until completion.
238    ///
239    /// See the `pdg_cook` example in the `/examples` folder.
240    pub fn cook_async<F>(&self, all_outputs: bool, mut func: F) -> Result<()>
241    where
242        F: FnMut(CookStep) -> Result<ControlFlow<bool>>,
243    {
244        let session = &self.node.session;
245        log::debug!("Start cooking PDG node: {}", self.node.path()?);
246        debug_assert!(session.is_valid());
247        ffi::cook_pdg(session, self.node.handle, false, false, all_outputs)?;
248        let mut events = create_events();
249        'main: loop {
250            let (graph_ids, graph_names) = ffi::get_pdg_contexts(session)?;
251            debug_assert_eq!(graph_ids.len(), graph_names.len());
252            for (graph_id, graph_name) in graph_ids.into_iter().zip(graph_names) {
253                for event in ffi::get_pdg_events(session, graph_id, &mut events)? {
254                    let event = PDGEventInfo(*event);
255                    match event.event_type() {
256                        PdgEventType::EventCookComplete => break 'main,
257                        _ => {
258                            match func(CookStep {
259                                event,
260                                graph_id,
261                                graph_name,
262                            }) {
263                                Err(e) => return Err(e),
264                                Ok(ControlFlow::Continue(_)) => {}
265                                Ok(ControlFlow::Break(stop_cooking)) => {
266                                    if stop_cooking {
267                                        // TODO: Should we call this for all graph ids?
268                                        ffi::cancel_pdg_cook(session, graph_id)?;
269                                    }
270                                    break 'main;
271                                }
272                            }
273                        }
274                    }
275                }
276            }
277        }
278        Ok(())
279    }
280
281    /// Trigger PDG cooking and wait for completion.
282    /// If all_outputs is true and this TOP node is of topnet type, cook all network outptus.
283    /// Results can then be retrieved from workitems with get_all_workitems()
284    pub fn cook_pdg_blocking(&self, all_outputs: bool) -> Result<()> {
285        ffi::cook_pdg(
286            &self.node.session,
287            self.node.handle,
288            false,
289            true,
290            all_outputs,
291        )
292    }
293
294    /// Get the graph(context) id of this node in PDG.
295    pub fn get_context_id(&self) -> Result<i32> {
296        ffi::get_pdg_context_id(&self.node.session, self.node.handle)
297    }
298
299    /// Cancel cooking.
300    pub fn cancel_cooking(&self) -> Result<()> {
301        log::debug!("Cancel PDG cooking {}", self.node.path()?);
302        let context = self.get_context_id()?;
303        ffi::cancel_pdg_cook(&self.node.session, context)
304    }
305
306    /// Pause cooking process
307    pub fn pause_cooking(&self) -> Result<()> {
308        log::debug!("Pause PDG cooking {}", self.node.path()?);
309        let context = self.get_context_id()?;
310        ffi::pause_pdg_cook(&self.node.session, context)
311    }
312
313    /// Dirty the node, forcing the work items to regenerate.
314    pub fn dirty_node(&self, clean_results: bool) -> Result<()> {
315        log::debug!("Set PDG node dirty {}", self.node.path()?);
316        ffi::dirty_pdg_node(&self.node.session, self.node.handle, clean_results)
317    }
318
319    /// Which this node current [`PdgState`]
320    pub fn get_current_state(&self, context_id: Option<i32>) -> Result<PdgState> {
321        let context = match context_id {
322            Some(c) => c,
323            None => self.get_context_id()?,
324        };
325        ffi::get_pdg_state(&self.node.session, context)
326    }
327
328    /// Get the work item by id and graph(context) id.
329    pub fn get_workitem(&self, workitem_id: WorkItemId) -> Result<PDGWorkItem<'_>> {
330        let context_id = self.get_context_id()?;
331        ffi::get_workitem_info(&self.node.session, context_id, workitem_id.0).map(|_| PDGWorkItem {
332            id: workitem_id,
333            context_id,
334            node: &self.node,
335        })
336    }
337
338    pub fn get_all_workitems(&self) -> Result<Vec<PDGWorkItem<'_>>> {
339        let context_id = self.get_context_id()?;
340        ffi::get_pdg_workitems(&self.node.session, self.node.handle).map(|vec| {
341            vec.into_iter()
342                .map(|id| PDGWorkItem {
343                    id: WorkItemId(id),
344                    context_id,
345                    node: &self.node,
346                })
347                .collect()
348        })
349    }
350
351    pub fn create_workitem(
352        &self,
353        name: &str,
354        index: i32,
355        context_id: Option<i32>,
356    ) -> Result<PDGWorkItem> {
357        let name = std::ffi::CString::new(name)?;
358        let context_id = match context_id {
359            Some(c) => c,
360            None => self.get_context_id()?,
361        };
362        let id = ffi::create_pdg_workitem(&self.node.session, self.node.handle, &name, index)?;
363        Ok(PDGWorkItem {
364            id: WorkItemId(id),
365            context_id,
366            node: &self.node,
367        })
368    }
369
370    pub fn commit_workitems(&self) -> Result<()> {
371        ffi::commit_pdg_workitems(&self.node.session, self.node.handle)
372    }
373}