Skip to main content

hapi_rs/attribute/
mod.rs

1//! Geometry attributes access and iterators
2//!
3//! Geometry attributes of different types are represented as trait objects
4//! and need to be downcast to concrete types
5//!
6//! ```
7//!
8//! use hapi_rs::session::new_in_process_session;
9//! use hapi_rs::session::SessionOptions;
10//! use hapi_rs::geometry::*;
11//! use hapi_rs::attribute::*;
12//! let session = new_in_process_session(Some(SessionOptions::default())).unwrap();
13//! let lib = session.load_asset_file("../otls/hapi_geo.hda").unwrap();
14//! let node = lib.try_create_first().unwrap();
15//! let geo = node.geometry().unwrap().unwrap();
16//! geo.node.cook_blocking().unwrap();
17//! let attr_p = geo.get_attribute(0, AttributeOwner::Point, "P").unwrap().expect("P exists");
18//! let attr_p = attr_p.downcast::<NumericAttr<f32>>().unwrap();
19//! attr_p.get(0).expect("read_attribute");
20//!
21//! ```
22mod array;
23#[cfg(feature = "async-cooking")]
24mod async_;
25mod bindings;
26
27use crate::errors::{ErrorContext, Result};
28pub use crate::ffi::AttributeInfo;
29pub use crate::ffi::enums::StorageType;
30use crate::node::HoudiniNode;
31use crate::stringhandle::StringArray;
32#[cfg(feature = "async-cooking")]
33use crate::stringhandle::StringHandle;
34#[cfg(feature = "async-cooking")]
35use crate::utils::i32_to_usize;
36use crate::utils::{i64_to_usize, uzize_to_i32};
37pub use array::*;
38#[cfg(feature = "async-cooking")]
39use async_::AsyncAttribResult;
40use std::any::Any;
41use std::borrow::Cow;
42use std::ffi::{CStr, CString};
43use std::marker::PhantomData;
44
45pub type JobId = i32;
46
47mod private {
48    pub trait Sealed {}
49}
50pub trait AttribValueType: private::Sealed + Clone + Default + Send + Sized + 'static {
51    fn storage() -> StorageType;
52    fn storage_array() -> StorageType;
53    fn get(
54        name: &CStr,
55        node: &HoudiniNode,
56        info: &AttributeInfo,
57        part_id: i32,
58        buffer: &mut Vec<Self>,
59    ) -> Result<()>;
60    #[cfg(feature = "async-cooking")]
61    fn get_async(
62        name: &CStr,
63        node: &HoudiniNode,
64        info: &AttributeInfo,
65        part_id: i32,
66        buffer: &mut Vec<Self>,
67    ) -> Result<JobId>;
68    fn set(
69        name: &CStr,
70        node: &HoudiniNode,
71        info: &AttributeInfo,
72        part_id: i32,
73        data: &[Self],
74        start: i32,
75        len: i32,
76    ) -> Result<()>;
77
78    #[cfg(feature = "async-cooking")]
79    fn set_async(
80        name: &CStr,
81        node: &HoudiniNode,
82        info: &AttributeInfo,
83        part: i32,
84        data: &[Self],
85        start: i32,
86        len: i32,
87    ) -> Result<JobId>;
88
89    fn set_unique(
90        name: &CStr,
91        node: &HoudiniNode,
92        info: &AttributeInfo,
93        part_id: i32,
94        data: &[Self],
95        start: i32,
96    ) -> Result<()>;
97
98    #[cfg(feature = "async-cooking")]
99    fn set_unique_async(
100        name: &CStr,
101        node: &HoudiniNode,
102        info: &AttributeInfo,
103        part_id: i32,
104        data: &[Self],
105        start: i32,
106    ) -> Result<JobId>;
107
108    fn get_array(
109        name: &CStr,
110        node: &HoudiniNode,
111        info: &AttributeInfo,
112        part: i32,
113    ) -> Result<DataArray<'static, Self>>
114    where
115        [Self]: ToOwned<Owned = Vec<Self>>;
116
117    #[cfg(feature = "async-cooking")]
118    fn get_array_async(
119        name: &CStr,
120        node: &HoudiniNode,
121        info: &AttributeInfo,
122        data: &mut [Self],
123        sizes: &mut [i32],
124        part: i32,
125    ) -> Result<JobId>;
126    fn set_array(
127        name: &CStr,
128        node: &HoudiniNode,
129        info: &AttributeInfo,
130        part: i32,
131        data: &[Self],
132        sizes: &[i32],
133    ) -> Result<()>
134    where
135        [Self]: ToOwned<Owned = Vec<Self>>;
136
137    #[cfg(feature = "async-cooking")]
138    fn set_array_async(
139        name: &CStr,
140        node: &HoudiniNode,
141        info: &AttributeInfo,
142        part: i32,
143        data: &[Self],
144        sizes: &[i32],
145    ) -> Result<JobId>;
146}
147
148macro_rules! impl_sealed {
149    ($($x:ident),+ $(,)?) => {
150        $(impl private::Sealed for $x {})+
151    }
152}
153
154impl_sealed!(u8, i8, i16, i32, i64, f32, f64);
155
156impl StorageType {
157    // Helper for matching array storage types to actual data type,
158    // e.g. StorageType::Array is actually an array of StorageType::Int,
159    // StorageType::FloatArray is StorageType::Float
160    pub(crate) fn type_matches(self, other: StorageType) -> bool {
161        use StorageType::{
162            Float, Float64, Float64Array, FloatArray, Int, Int8Array, Int16, Int16Array, Int64,
163            Int64Array, IntArray, StringArray, Uint8, Uint8Array,
164        };
165        match other {
166            IntArray | Uint8Array | Int8Array | Int16Array | Int64Array => {
167                matches!(self, Int | Uint8 | Int16 | Int64)
168            }
169            FloatArray | Float64Array => matches!(self, Float | Float64),
170            StringArray => matches!(self, StringArray),
171            _st => matches!(self, _st),
172        }
173    }
174}
175
176pub(crate) struct AttributeBundle {
177    pub(crate) info: AttributeInfo,
178    pub(crate) name: CString,
179    pub(crate) node: HoudiniNode,
180}
181
182pub struct NumericAttr<T: AttribValueType>(pub(crate) AttributeBundle, PhantomData<T>);
183
184pub struct NumericArrayAttr<T: AttribValueType>(pub(crate) AttributeBundle, PhantomData<T>);
185
186pub struct StringAttr(pub(crate) AttributeBundle);
187
188pub struct StringArrayAttr(pub(crate) AttributeBundle);
189
190pub struct DictionaryAttr(pub(crate) AttributeBundle);
191
192pub struct DictionaryArrayAttr(pub(crate) AttributeBundle);
193
194impl<T: AttribValueType> NumericArrayAttr<T>
195where
196    [T]: ToOwned<Owned = Vec<T>>,
197{
198    pub(crate) fn new(
199        name: CString,
200        info: AttributeInfo,
201        node: HoudiniNode,
202    ) -> NumericArrayAttr<T> {
203        NumericArrayAttr(AttributeBundle { info, name, node }, PhantomData)
204    }
205    pub fn get(&self, part_id: i32) -> Result<DataArray<'_, T>> {
206        debug_assert!(self.0.info.storage().type_matches(T::storage()));
207        T::get_array(&self.0.name, &self.0.node, &self.0.info, part_id)
208    }
209
210    #[cfg(feature = "async-cooking")]
211    pub fn get_async(&self, part_id: i32) -> Result<(JobId, DataArray<'_, T>)> {
212        let info = &self.0.info;
213        debug_assert!(info.storage().type_matches(T::storage()));
214        let mut data = vec![T::default(); i64_to_usize(info.total_array_elements())];
215        let mut sizes = vec![0i32; i32_to_usize(info.count())];
216        let job_id = T::get_array_async(
217            &self.0.name,
218            &self.0.node,
219            info,
220            &mut data,
221            &mut sizes,
222            part_id,
223        )?;
224        Ok((job_id, DataArray::new_owned(data, sizes)))
225    }
226
227    pub fn set(&self, part_id: i32, values: &DataArray<T>) -> Result<()> {
228        debug_assert!(self.0.info.storage().type_matches(T::storage()));
229        debug_assert_eq!(
230            self.0.info.count(),
231            uzize_to_i32(values.sizes().len()),
232            "sizes array must be the same as AttributeInfo::count"
233        );
234        debug_assert_eq!(
235            i64_to_usize(self.0.info.total_array_elements()),
236            values.data().len(),
237            "data array must be the same as AttributeInfo::total_array_elements"
238        );
239        T::set_array(
240            &self.0.name,
241            &self.0.node,
242            &self.0.info,
243            part_id,
244            values.data(),
245            values.sizes(),
246        )
247    }
248}
249
250impl<T: AttribValueType> NumericAttr<T> {
251    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> NumericAttr<T> {
252        NumericAttr(AttributeBundle { info, name, node }, PhantomData)
253    }
254    /// Get attribute value. Allocates a new vector on every call
255    pub fn get(&self, part_id: i32) -> Result<Vec<T>> {
256        debug_assert_eq!(self.0.info.storage(), T::storage());
257        let mut buffer = vec![];
258        T::get(
259            &self.0.name,
260            &self.0.node,
261            &self.0.info,
262            part_id,
263            &mut buffer,
264        )?;
265        Ok(buffer)
266    }
267    /// Start filling a given buffer asynchronously and return a job id.
268    /// It's important to keep the buffer alive until the job is complete
269    #[cfg(feature = "async-cooking")]
270    pub fn read_async_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<i32> {
271        // TODO: Get an updated attribute info since point count can change between calls.
272        // but there's looks like some use after free on the C side, when AttributeInfo gets
273        // accessed after it gets dropped in Rust, so we can't get new AttributeInfo here.
274        let info = &self.0.info;
275        buffer.resize((info.count() * info.tuple_size()) as usize, T::default());
276        T::get_async(&self.0.name, &self.0.node, info, part_id, buffer).with_context(|| {
277            format!(
278                "Reading attribute \"{}\" asynchronously, into buffer size {}",
279                self.0.name.to_string_lossy(),
280                buffer.len()
281            )
282        })
283    }
284
285    #[cfg(feature = "async-cooking")]
286    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<T>> {
287        let info = &self.0.info;
288        let size = (info.count() * info.tuple_size()) as usize;
289        let mut data = Vec::<T>::with_capacity(size);
290        let job_id = T::get_async(&self.0.name, &self.0.node, info, part_id, &mut data)
291            .with_context(|| {
292                format!(
293                    "Reading attribute {} asynchronously",
294                    self.0.name.to_string_lossy()
295                )
296            })?;
297        Ok(AsyncAttribResult {
298            job_id,
299            data,
300            size,
301            session: self.0.node.session.clone(),
302        })
303    }
304
305    /// Read the attribute data into a provided buffer. The buffer will be auto-resized
306    /// from the attribute info.
307    pub fn read_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<()> {
308        debug_assert_eq!(self.0.info.storage(), T::storage());
309        // Get an updated attribute info since point count can change between calls
310        let info = AttributeInfo::new(&self.0.node, part_id, self.0.info.owner(), &self.0.name)?;
311        T::get(&self.0.name, &self.0.node, &info, part_id, buffer)
312    }
313    pub fn set(&self, part_id: i32, values: &[T]) -> Result<()> {
314        debug_assert_eq!(self.0.info.storage(), T::storage());
315        T::set(
316            &self.0.name,
317            &self.0.node,
318            &self.0.info,
319            part_id,
320            values,
321            0,
322            self.0.info.count().min(uzize_to_i32(values.len())),
323        )
324    }
325
326    /// Set multiple attribute data to the same value.
327    /// value length must be less or equal to attribute tuple size.
328    pub fn set_unique(&self, part_id: i32, value: &[T]) -> Result<()> {
329        debug_assert_eq!(self.0.info.storage(), T::storage());
330        debug_assert!(value.len() <= self.0.info.tuple_size() as usize);
331        T::set_unique(&self.0.name, &self.0.node, &self.0.info, part_id, value, 0)
332    }
333}
334
335impl StringAttr {
336    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> StringAttr {
337        StringAttr(AttributeBundle { info, name, node })
338    }
339    pub fn get(&self, part_id: i32) -> Result<StringArray> {
340        debug_assert!(self.0.node.is_valid()?);
341        bindings::get_attribute_string_data(
342            &self.0.node,
343            part_id,
344            self.0.name.as_c_str(),
345            &self.0.info.0,
346        )
347    }
348
349    #[cfg(feature = "async-cooking")]
350    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<StringHandle>> {
351        bindings::get_attribute_string_data_async(
352            &self.0.node,
353            part_id,
354            self.0.name.as_c_str(),
355            &self.0.info.0,
356        )
357    }
358
359    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<()> {
360        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
361        bindings::set_attribute_string_data(
362            &self.0.node,
363            part_id,
364            self.0.name.as_c_str(),
365            &self.0.info.0,
366            ptrs.as_mut(),
367        )
368    }
369
370    #[cfg(feature = "async-cooking")]
371    pub fn set_async(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<JobId> {
372        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
373        bindings::set_attribute_string_data_async(
374            &self.0.node,
375            self.0.name.as_c_str(),
376            part_id,
377            &self.0.info.0,
378            ptrs.as_mut(),
379        )
380    }
381    /// Set multiple attribute data to the same value.
382    /// value length must be less or equal to attribute tuple size.
383    pub fn set_unique(&self, part: i32, value: &CStr) -> Result<()> {
384        bindings::set_attribute_string_unique_data(
385            &self.0.node,
386            self.0.name.as_c_str(),
387            &self.0.info.0,
388            part,
389            value.as_ptr(),
390        )
391    }
392
393    /// Set multiple attribute string data to the same unique value asynchronously.
394    #[cfg(feature = "async-cooking")]
395    pub fn set_unique_async(&self, part: i32, value: &CStr) -> Result<JobId> {
396        bindings::set_attribute_string_unique_data_async(
397            &self.0.node,
398            self.0.name.as_c_str(),
399            &self.0.info.0,
400            part,
401            value.as_ptr(),
402        )
403    }
404
405    /// Set attribute string data by index.
406    pub fn set_indexed(
407        &self,
408        part_id: i32,
409        values: &[impl AsRef<CStr>],
410        indices: &[i32],
411    ) -> Result<()> {
412        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
413        bindings::set_attribute_indexed_string_data(
414            &self.0.node,
415            part_id,
416            self.0.name.as_c_str(),
417            &self.0.info.0,
418            ptrs.as_mut(),
419            indices,
420        )
421    }
422
423    #[cfg(feature = "async-cooking")]
424    pub fn set_indexed_async(
425        &self,
426        part_id: i32,
427        values: &[impl AsRef<CStr>],
428        indices: &[i32],
429    ) -> Result<JobId> {
430        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
431        bindings::set_attribute_indexed_string_data_async(
432            &self.0.node,
433            part_id,
434            self.0.name.as_c_str(),
435            &self.0.info.0,
436            ptrs.as_mut(),
437            indices,
438        )
439    }
440}
441
442impl StringArrayAttr {
443    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> StringArrayAttr {
444        StringArrayAttr(AttributeBundle { info, name, node })
445    }
446    pub fn get(&self, part_id: i32) -> Result<StringMultiArray> {
447        debug_assert!(self.0.node.is_valid()?);
448        bindings::get_attribute_string_array_data(
449            &self.0.node,
450            self.0.name.as_c_str(),
451            part_id,
452            &self.0.info,
453        )
454    }
455
456    #[cfg(feature = "async-cooking")]
457    pub fn get_async(&self, part_id: i32) -> Result<(JobId, StringMultiArray)> {
458        let total_array_elements = i64_to_usize(self.0.info.total_array_elements());
459        let mut handles = vec![StringHandle(-1); total_array_elements];
460        let mut sizes = vec![0; self.0.info.count() as usize];
461        let job_id = bindings::get_attribute_string_array_data_async(
462            &self.0.node,
463            self.0.name.as_c_str(),
464            part_id,
465            &self.0.info.0,
466            &mut handles,
467            &mut sizes,
468        )?;
469        Ok((
470            job_id,
471            StringMultiArray {
472                handles,
473                sizes,
474                session: debug_ignore::DebugIgnore(self.0.node.session.clone()),
475            },
476        ))
477    }
478    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>], sizes: &[i32]) -> Result<()> {
479        debug_assert!(self.0.node.is_valid()?);
480        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
481        bindings::set_attribute_string_array_data(
482            &self.0.node,
483            self.0.name.as_c_str(),
484            &self.0.info.0,
485            part_id,
486            ptrs.as_mut(),
487            sizes,
488        )
489    }
490
491    #[cfg(feature = "async-cooking")]
492    pub fn set_async(
493        &self,
494        part_id: i32,
495        values: &[impl AsRef<CStr>],
496        sizes: &[i32],
497    ) -> Result<JobId> {
498        debug_assert!(self.0.node.is_valid()?);
499        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
500        bindings::set_attribute_string_array_data_async(
501            &self.0.node,
502            self.0.name.as_c_str(),
503            part_id,
504            &self.0.info.0,
505            ptrs.as_mut(),
506            sizes,
507        )
508    }
509}
510
511impl DictionaryAttr {
512    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> Self {
513        DictionaryAttr(AttributeBundle { info, name, node })
514    }
515
516    pub fn get(&self, part_id: i32) -> Result<StringArray> {
517        debug_assert!(self.0.node.is_valid()?);
518        bindings::get_attribute_dictionary_data(
519            &self.0.node,
520            part_id,
521            self.0.name.as_c_str(),
522            &self.0.info.0,
523        )
524    }
525
526    #[cfg(feature = "async-cooking")]
527    pub fn set_async(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<JobId> {
528        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
529        bindings::set_attribute_dictionary_data_async(
530            &self.0.node,
531            self.0.name.as_c_str(),
532            part_id,
533            &self.0.info.0,
534            ptrs.as_mut(),
535        )
536    }
537
538    #[cfg(feature = "async-cooking")]
539    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<StringHandle>> {
540        bindings::get_attribute_dictionary_data_async(
541            &self.0.node,
542            part_id,
543            self.0.name.as_c_str(),
544            &self.0.info.0,
545        )
546    }
547
548    /// Set dictionary attribute values where each string should be a JSON-encoded value.
549    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<()> {
550        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
551        bindings::set_attribute_dictionary_data(
552            &self.0.node,
553            part_id,
554            self.0.name.as_c_str(),
555            &self.0.info.0,
556            ptrs.as_mut(),
557        )
558    }
559}
560
561impl DictionaryArrayAttr {
562    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> Self {
563        DictionaryArrayAttr(AttributeBundle { info, name, node })
564    }
565    pub fn get(&self, part_id: i32) -> Result<StringMultiArray> {
566        debug_assert!(self.0.node.is_valid()?);
567        bindings::get_attribute_dictionary_array_data(
568            &self.0.node,
569            &self.0.name,
570            part_id,
571            &self.0.info,
572        )
573    }
574
575    #[cfg(feature = "async-cooking")]
576    pub fn get_async(&self, part_id: i32) -> Result<(JobId, StringMultiArray)> {
577        let array_elements = i64_to_usize(self.0.info.total_array_elements());
578        let mut handles = vec![StringHandle(-1); array_elements];
579        let mut sizes = vec![0; self.0.info.count() as usize];
580        let job_id = bindings::get_attribute_dictionary_array_data_async(
581            &self.0.node,
582            self.0.name.as_c_str(),
583            part_id,
584            &self.0.info.0,
585            &mut handles,
586            &mut sizes,
587        )?;
588        Ok((
589            job_id,
590            StringMultiArray {
591                handles,
592                sizes,
593                session: debug_ignore::DebugIgnore(self.0.node.session.clone()),
594            },
595        ))
596    }
597    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>], sizes: &[i32]) -> Result<()> {
598        debug_assert!(self.0.node.is_valid()?);
599        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
600        bindings::set_attribute_dictionary_array_data(
601            &self.0.node,
602            self.0.name.as_c_str(),
603            &self.0.info.0,
604            part_id,
605            ptrs.as_mut(),
606            sizes,
607        )
608    }
609
610    #[cfg(feature = "async-cooking")]
611    pub fn set_async(
612        &self,
613        part_id: i32,
614        values: &[impl AsRef<CStr>],
615        sizes: &[i32],
616    ) -> Result<JobId> {
617        debug_assert!(self.0.node.is_valid()?);
618        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
619        bindings::set_attribute_dictionary_array_data_async(
620            &self.0.node,
621            self.0.name.as_c_str(),
622            part_id,
623            &self.0.info.0,
624            ptrs.as_mut(),
625            sizes,
626        )
627    }
628}
629
630#[doc(hidden)]
631pub trait AsAttribute: Send {
632    fn info(&self) -> &AttributeInfo;
633    fn storage(&self) -> StorageType;
634    fn boxed(self) -> Box<dyn AnyAttribWrapper>
635    where
636        Self: Sized + 'static,
637    {
638        Box::new(self)
639    }
640    fn name(&self) -> &CStr;
641    fn node(&self) -> &HoudiniNode;
642}
643
644impl<T: AttribValueType> AsAttribute for NumericAttr<T> {
645    fn info(&self) -> &AttributeInfo {
646        &self.0.info
647    }
648    fn storage(&self) -> StorageType {
649        T::storage()
650    }
651
652    fn name(&self) -> &CStr {
653        &self.0.name
654    }
655
656    fn node(&self) -> &HoudiniNode {
657        &self.0.node
658    }
659}
660
661impl<T: AttribValueType> AsAttribute for NumericArrayAttr<T> {
662    fn info(&self) -> &AttributeInfo {
663        &self.0.info
664    }
665    fn storage(&self) -> StorageType {
666        T::storage()
667    }
668    fn name(&self) -> &CStr {
669        &self.0.name
670    }
671    fn node(&self) -> &HoudiniNode {
672        &self.0.node
673    }
674}
675
676impl AsAttribute for StringAttr {
677    fn info(&self) -> &AttributeInfo {
678        &self.0.info
679    }
680
681    fn storage(&self) -> StorageType {
682        StorageType::String
683    }
684
685    fn name(&self) -> &CStr {
686        &self.0.name
687    }
688
689    fn node(&self) -> &HoudiniNode {
690        &self.0.node
691    }
692}
693
694impl AsAttribute for StringArrayAttr {
695    fn info(&self) -> &AttributeInfo {
696        &self.0.info
697    }
698
699    fn storage(&self) -> StorageType {
700        StorageType::StringArray
701    }
702
703    fn name(&self) -> &CStr {
704        &self.0.name
705    }
706
707    fn node(&self) -> &HoudiniNode {
708        &self.0.node
709    }
710}
711
712impl AsAttribute for DictionaryAttr {
713    fn info(&self) -> &AttributeInfo {
714        &self.0.info
715    }
716
717    fn storage(&self) -> StorageType {
718        StorageType::Dictionary
719    }
720
721    fn name(&self) -> &CStr {
722        &self.0.name
723    }
724
725    fn node(&self) -> &HoudiniNode {
726        &self.0.node
727    }
728}
729
730impl AsAttribute for DictionaryArrayAttr {
731    fn info(&self) -> &AttributeInfo {
732        &self.0.info
733    }
734
735    fn storage(&self) -> StorageType {
736        StorageType::DictionaryArray
737    }
738
739    fn name(&self) -> &CStr {
740        &self.0.name
741    }
742
743    fn node(&self) -> &HoudiniNode {
744        &self.0.node
745    }
746}
747
748#[doc(hidden)]
749pub trait AnyAttribWrapper: Any + AsAttribute {
750    fn as_any(&self) -> &dyn Any;
751}
752
753impl<T: AsAttribute + 'static> AnyAttribWrapper for T {
754    fn as_any(&self) -> &dyn Any {
755        self
756    }
757}
758
759pub struct Attribute(Box<dyn AnyAttribWrapper>);
760
761impl Attribute {
762    pub(crate) fn new(attr_obj: Box<dyn AnyAttribWrapper>) -> Self {
763        Attribute(attr_obj)
764    }
765    #[must_use]
766    pub fn downcast<T: AnyAttribWrapper>(&self) -> Option<&T> {
767        self.0.as_any().downcast_ref::<T>()
768    }
769    #[must_use]
770    pub fn name(&self) -> Cow<'_, str> {
771        self.0.name().to_string_lossy()
772    }
773    #[must_use]
774    pub fn storage(&self) -> StorageType {
775        self.0.storage()
776    }
777    #[must_use]
778    pub fn info(&self) -> &AttributeInfo {
779        self.0.info()
780    }
781    pub fn delete(self, part_id: i32) -> Result<()> {
782        crate::ffi::delete_attribute(self.0.node(), part_id, self.0.name(), &self.0.info().0)
783    }
784}