hapi_rs/
pdg.rs

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