1mod 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 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 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 #[cfg(feature = "async-cooking")]
270 pub fn read_async_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<i32> {
271 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 pub fn read_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<()> {
308 debug_assert_eq!(self.0.info.storage(), T::storage());
309 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 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 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 #[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 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 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}