Skip to main content

nsi_ffi_wrap/
argument.rs

1//! Optional arguments passed to methods of an ɴsɪ context.
2use enum_dispatch::enum_dispatch;
3use nsi_sys::*;
4use std::{
5    ffi::{CString, c_void},
6    marker::PhantomData,
7    pin::Pin,
8};
9use ustr::{Ustr, ustr};
10
11// Needed for docs.
12#[allow(unused_imports)]
13use crate::*;
14
15#[inline(always)]
16pub(crate) fn get_c_param_vec(
17    args: Option<&ArgSlice>,
18) -> (i32, *const NSIParam, Vec<NSIParam>) {
19    let args = match args {
20        Some(args) => args
21            .iter()
22            .map(|arg| NSIParam {
23                name: arg.name.as_char_ptr(),
24                data: arg.data.as_c_ptr(),
25                type_: arg.data.type_() as _,
26                arraylength: arg.array_length as _,
27                count: (arg.data.len() / arg.array_length) as _,
28                flags: arg.flags as _,
29            })
30            .collect::<Vec<_>>(),
31        None => Vec::new(),
32    };
33
34    (args.len() as _, args.as_ptr(), args)
35}
36
37/// A slice of (optional) arguments passed to a method of
38/// [`Context`].
39pub type ArgSlice<'a, 'b> = [Arg<'a, 'b>];
40
41/// A vector of (optional) arguments passed to a method of
42/// [`Context`].
43pub type ArgVec<'a, 'b> = Vec<Arg<'a, 'b>>;
44
45/// An (optional) argument passed to a method of
46/// [`Context`].
47#[derive(Debug, Clone)]
48pub struct Arg<'a, 'b> {
49    pub(crate) name: Ustr,
50    pub(crate) data: ArgData<'a, 'b>,
51    // Length of each element if an array type.
52    pub(crate) array_length: usize,
53    // Number of elements.
54    pub(crate) flags: i32,
55}
56
57impl<'a, 'b> Arg<'a, 'b> {
58    #[inline]
59    pub fn new(name: &str, data: ArgData<'a, 'b>) -> Self {
60        Arg {
61            name: ustr(name),
62            data,
63            array_length: 1,
64            flags: 0,
65        }
66    }
67
68    /// Sets the length of the argument for each element.
69    #[inline]
70    pub fn array_len(mut self, length: usize) -> Self {
71        self.array_length = length;
72        self.flags |= NSIParamFlags::IsArray.bits();
73        self
74    }
75
76    /// Marks this argument as having per-face granularity.
77    #[inline]
78    pub fn per_face(mut self) -> Self {
79        self.flags |= NSIParamFlags::PerFace.bits();
80        self
81    }
82
83    /// Marks this argument as having per-vertex granularity.
84    #[inline]
85    pub fn per_vertex(mut self) -> Self {
86        self.flags |= NSIParamFlags::PerVertex.bits();
87        self
88    }
89
90    /// Marks this argument as to be interpolated linearly.
91    #[inline]
92    pub fn linear_interpolation(mut self) -> Self {
93        self.flags |= NSIParamFlags::InterpolateLinear.bits();
94        self
95    }
96}
97
98#[enum_dispatch(ArgData)]
99pub(crate) trait ArgDataMethods {
100    //const TYPE: DataType;
101    fn type_(&self) -> DataType;
102    fn len(&self) -> usize;
103    fn as_c_ptr(&self) -> *const c_void;
104}
105
106/// A variant describing data passed to the renderer.
107///
108/// # Lifetimes
109/// Lifetime `'a` is for any tuple or array type as these are
110/// passed as references and only need to live as long as the
111/// function call where they get passed.
112///
113/// Lifetime `'b` is for the arbitrary reference type. This is
114/// pegged to the lifetime of the [`Context`](crate::context::Context).
115/// Use this to pass arbitrary Rust data through the FFI boundary.
116#[enum_dispatch]
117#[derive(Debug, Clone)]
118pub enum ArgData<'a, 'b> {
119    /// Single [`f32`] value.
120    F32,
121    /// An [`f32`] slice.
122    F32Slice(F32Slice<'a>),
123    /// Single [`f64`] value.
124    F64,
125    /// An [`f64`] slice.
126    F64Slice(F64Slice<'a>),
127    /// Single [`i32`] value.
128    I32,
129    /// An [`i32`] slice.
130    I32Slice(I32Slice<'a>),
131    /// Single [`i64`] value.
132    I64,
133    /// An [`i64`] slice.
134    I64Slice(I64Slice<'a>),
135    /// A [`String`].
136    String(String),
137    /// A [`String`] slice.
138    StringSlice(StringSlice),
139    /// Color in linear space, given as a red, green, blue triplet
140    /// of [`f32`] values; usually in the range `0..1`.
141    Color(Color<'a>),
142    /// A flat [`f32`] slice of colors (`len % 3 == 0`).
143    ColorSlice(ColorSlice<'a>),
144    /// Point, given as three [`f32`] values.
145    Point(Point<'a>),
146    /// A flat [`f32`] slice of points (`len % 3 == 0`).
147    PointSlice(PointSlice<'a>),
148    /// Vector, given as three [`f32`] values.
149    Vector(Vector<'a>),
150    /// A flat [`f32`] slice of vectors (`len % 3 == 0`).
151    VectorSlice(VectorSlice<'a>),
152    /// Normal vector, given as three [`f32`] values.
153    Normal(Normal<'a>),
154    /// A flat [`f32`] slice of normals (`len % 3 == 0`).
155    NormalSlice(NormalSlice<'a>),
156    /// Row-major, 4×4 transformation matrix, given as 16 [`f32`] values.
157    MatrixF32(MatrixF32<'a>),
158    /// A flat [`f32`] slice of matrices (`len % 16 == 0`).
159    MatrixF32Slice(MatrixF32Slice<'a>),
160    /// Row-major, 4×4 transformation matrix, given as 16 [`f64`] values.
161    MatrixF64(MatrixF64<'a>),
162    /// A flat [`f64`] slice of matrices (`len % 16 == 0`).
163    MatrixF64Slice(MatrixF64Slice<'a>),
164    /// A slice of 4-component f32 points (xyzw).
165    /// Wire-side: a flat `NSITypeFloat` slice of `4 * N` floats — the
166    /// renderer groups them by attribute semantics. Use the
167    /// [`point4_f32_slice!`][crate::point4_f32_slice] macro to keep
168    /// `&[[f32; 4]]` ergonomics in Rust while the FFI sees the flat
169    /// layout.
170    Point4F32Slice(Point4F32Slice<'a>),
171    /// Reference *with* lifetime guarantees.
172    ///
173    /// This gets converted to a raw pointer when passed
174    /// through the FFI boundary.
175    ///
176    /// ```
177    /// # use nsi_ffi_wrap as nsi;
178    /// let ctx = nsi::Context::new(None).unwrap();
179    ///
180    /// // Lots of scene setup omitted ...
181    ///
182    /// // Setup a custom output driver and send
183    /// // a payload to it through the FFI boundary.
184    /// ctx.create("driver", nsi::OUTPUT_DRIVER, None);
185    /// ctx.connect("driver", None, "beauty", "outputdrivers", None);
186    ///
187    /// struct Payload {
188    ///     some_data: u32,
189    /// }
190    ///
191    /// // Must use heap allocation for stable address
192    /// let payload = Box::new(Payload { some_data: 42 });
193    /// ctx.set_attribute(
194    ///     "driver",
195    ///     &[
196    ///         nsi::string!("drivername", "custom_driver"),
197    ///         // Payload gets sent as raw pointer through
198    ///         // the FFI boundary. The Box ensures stable address.
199    ///         nsi::reference!("payload", &payload),
200    ///     ],
201    /// );
202    ///
203    /// // We need to explicitly call drop here as
204    /// // ctx's lifetime is pegged to that of payload.
205    /// drop(ctx);
206    /// ```
207    Reference(Reference<'b>),
208    /// A [`Reference`] slice.
209    ReferenceSlice(ReferenceSlice<'b>),
210    /// A callback.
211    Callback(Callback<'b>),
212}
213
214macro_rules! nsi_data_def {
215    ($type: ty, $name: ident, $nsi_type: expr) => {
216        /// See [`ArgData`] for details.
217        #[derive(Debug, Clone, PartialEq)]
218        pub struct $name {
219            data: $type,
220        }
221
222        impl $name {
223            pub fn new(data: $type) -> Self {
224                Self { data }
225            }
226        }
227
228        impl ArgDataMethods for $name {
229            fn type_(&self) -> DataType {
230                $nsi_type
231            }
232
233            fn len(&self) -> usize {
234                1
235            }
236
237            fn as_c_ptr(&self) -> *const c_void {
238                &self.data as *const $type as _
239            }
240        }
241    };
242}
243
244macro_rules! nsi_data_array_def {
245    ($type: ty, $name: ident, $nsi_type: expr) => {
246        /// See [`ArgData`] for details.
247        #[derive(Debug, Clone, PartialEq)]
248        pub struct $name<'a> {
249            data: &'a [$type],
250        }
251
252        impl<'a> $name<'a> {
253            pub fn new(data: &'a [$type]) -> Self {
254                //debug_assert_eq!(0, data.len() % $nsi_type.elemensize());
255                Self { data }
256            }
257        }
258
259        impl<'a> ArgDataMethods for $name<'a> {
260            fn type_(&self) -> DataType {
261                $nsi_type
262            }
263
264            fn len(&self) -> usize {
265                self.data.len() // / $nsi_type.elemensize()
266            }
267
268            fn as_c_ptr(&self) -> *const c_void {
269                self.data.as_ptr() as _
270            }
271        }
272    };
273}
274
275macro_rules! nsi_tuple_data_array_def {
276    ($type: ty, $name: ident, $nsi_type: expr, $len: expr ) => {
277        /// See [`ArgData`] for details.
278        #[derive(Debug, Clone, PartialEq)]
279        pub struct $name<'a> {
280            data: &'a [[$type; $len]],
281        }
282
283        impl<'a> $name<'a> {
284            pub fn new(data: &'a [[$type; $len]]) -> Self {
285                //debug_assert_eq!(0, data.len() % $nsi_type.elemensize());
286                Self { data }
287            }
288        }
289
290        impl<'a> ArgDataMethods for $name<'a> {
291            fn type_(&self) -> DataType {
292                $nsi_type
293            }
294
295            fn len(&self) -> usize {
296                self.data.len() // / $nsi_type.elemensize()
297            }
298
299            fn as_c_ptr(&self) -> *const c_void {
300                self.data.as_ptr() as _
301            }
302        }
303    };
304}
305
306macro_rules! nsi_tuple_data_def {
307    ($type: tt, $len: expr, $name: ident, $nsi_type: expr) => {
308        /// See [`ArgData`] for details.
309        #[derive(Debug, Clone, PartialEq)]
310        pub struct $name<'a> {
311            data: &'a [$type; $len],
312        }
313
314        impl<'a> $name<'a> {
315            pub fn new(data: &'a [$type; $len]) -> Self {
316                Self { data }
317            }
318        }
319
320        impl<'a> ArgDataMethods for $name<'a> {
321            fn type_(&self) -> DataType {
322                $nsi_type
323            }
324
325            fn len(&self) -> usize {
326                1
327            }
328
329            fn as_c_ptr(&self) -> *const c_void {
330                self.data.as_ptr() as _
331            }
332        }
333    };
334}
335
336nsi_data_def!(f32, F32, DataType::F32);
337nsi_data_def!(f64, F64, DataType::F64);
338nsi_data_def!(i32, I32, DataType::I32);
339nsi_data_def!(i64, I64, DataType::I64);
340
341/// See [`ArgData`] for details.
342/// A reference to data that will be passed through FFI.
343///
344/// # Safety
345/// The referenced data must outlive the NSI context that uses this reference.
346/// The data must remain at a stable memory address.
347///
348/// This type now properly enforces pinning by requiring heap-allocated data
349/// through Box, Arc, or similar types that guarantee stable addresses.
350#[derive(Debug, Clone)]
351pub struct Reference<'a> {
352    data: *const c_void,
353    _marker: PhantomData<&'a ()>,
354}
355
356// SAFETY: Reference only contains a pointer and doesn't dereference it.
357// The actual safety depends on the lifetime parameter being correct.
358unsafe impl Send for Reference<'static> {}
359unsafe impl Sync for Reference<'static> {}
360
361/// Trait for types that can be safely converted to a Reference.
362/// This is implemented only for types that guarantee stable memory addresses.
363pub trait StableDeref<'a> {
364    /// Get a stable pointer to the data
365    fn stable_deref(&self) -> *const c_void;
366}
367
368impl<'a, T: ?Sized> StableDeref<'a> for &'a Box<T> {
369    fn stable_deref(&self) -> *const c_void {
370        self.as_ref() as *const T as *const c_void
371    }
372}
373
374impl<'a, T: ?Sized> StableDeref<'a> for &'a Arc<T> {
375    fn stable_deref(&self) -> *const c_void {
376        self.as_ref() as *const T as *const c_void
377    }
378}
379
380impl<'a, T: ?Sized> StableDeref<'a> for &'a Pin<Box<T>> {
381    fn stable_deref(&self) -> *const c_void {
382        self.as_ref().get_ref() as *const T as *const c_void
383    }
384}
385
386use std::sync::Arc;
387
388impl<'a> Reference<'a> {
389    /// Create a reference from any type that implements StableDeref.
390    /// This includes &Box<T>, &Arc<T>, and &Pin<Box<T>>.
391    pub fn new<S: StableDeref<'a>>(data: S) -> Self {
392        let ptr = data.stable_deref();
393        debug_assert!(!ptr.is_null(), "Reference created with null pointer");
394
395        Self {
396            data: ptr,
397            _marker: PhantomData,
398        }
399    }
400
401    /// Create a reference from a Box.
402    ///
403    /// Box guarantees a stable heap address, making it safe for FFI.
404    #[allow(clippy::borrowed_box)]
405    pub fn from_box<T: ?Sized>(data: &'a Box<T>) -> Self {
406        let ptr = data.as_ref() as *const T as *const c_void;
407        debug_assert!(!ptr.is_null(), "Reference created with null pointer");
408
409        Self {
410            data: ptr,
411            _marker: PhantomData,
412        }
413    }
414
415    /// Create a reference from an Arc.
416    ///
417    /// Arc guarantees a stable heap address, making it safe for FFI.
418    pub fn from_arc<T: ?Sized>(data: &'a Arc<T>) -> Self {
419        let ptr = data.as_ref() as *const T as *const c_void;
420        debug_assert!(!ptr.is_null(), "Reference created with null pointer");
421
422        Self {
423            data: ptr,
424            _marker: PhantomData,
425        }
426    }
427
428    /// Create a reference from a pinned Box.
429    ///
430    /// This is the safest option as it guarantees the data cannot be moved.
431    pub fn from_pin_box<T: ?Sized>(data: &'a Pin<Box<T>>) -> Self {
432        let ptr = data.as_ref().get_ref() as *const T as *const c_void;
433        debug_assert!(!ptr.is_null(), "Reference created with null pointer");
434
435        Self {
436            data: ptr,
437            _marker: PhantomData,
438        }
439    }
440
441    /// Create a reference from data with a stable address.
442    ///
443    /// # Safety
444    /// The caller must guarantee that `data` has a stable address for its entire
445    /// lifetime. This is automatically true for:
446    /// - Heap allocated types (Box, Arc, Vec's data, String's data)
447    /// - Static data
448    /// - Pinned data
449    ///
450    /// This is NOT safe for:
451    /// - Stack allocated data that might move
452    /// - Data inside collections that might reallocate
453    pub unsafe fn from_stable<T: ?Sized>(data: &'a T) -> Self {
454        let ptr = data as *const T as *const c_void;
455        debug_assert!(!ptr.is_null(), "Reference created with null pointer");
456
457        Self {
458            data: ptr,
459            _marker: PhantomData,
460        }
461    }
462
463    /// Create a reference from a raw pointer.
464    ///
465    /// # Safety
466    /// The caller must ensure that:
467    /// 1. The pointer is valid and non-null
468    /// 2. The pointed-to data outlives 'a
469    /// 3. The data won't be moved or freed while this reference exists
470    pub unsafe fn from_ptr(ptr: *const c_void) -> Self {
471        debug_assert!(!ptr.is_null(), "Reference created with null pointer");
472        Self {
473            data: ptr,
474            _marker: PhantomData,
475        }
476    }
477}
478
479impl<'a> ArgDataMethods for Reference<'a> {
480    fn type_(&self) -> DataType {
481        DataType::Reference
482    }
483
484    fn len(&self) -> usize {
485        1
486    }
487
488    fn as_c_ptr(&self) -> *const c_void {
489        self.data
490    }
491}
492
493pub trait CallbackPtr {
494    #[doc(hidden)]
495    #[allow(clippy::wrong_self_convention)]
496    fn to_ptr(self) -> *const c_void;
497}
498
499unsafe impl Send for Callback<'static> {}
500unsafe impl Sync for Callback<'static> {}
501
502/// See [`ArgData`] for details.
503#[derive(Debug, Clone)]
504pub struct Callback<'a> {
505    data: *const c_void,
506    _marker: PhantomData<&'a mut ()>,
507}
508
509impl<'a> Callback<'a> {
510    pub fn new<T: CallbackPtr>(data: T) -> Self {
511        Self {
512            data: data.to_ptr(),
513            _marker: PhantomData,
514        }
515    }
516}
517
518impl<'a> ArgDataMethods for Callback<'a> {
519    fn type_(&self) -> DataType {
520        DataType::Reference
521    }
522
523    fn len(&self) -> usize {
524        1
525    }
526
527    fn as_c_ptr(&self) -> *const c_void {
528        self.data
529    }
530}
531
532/// See [`ArgData`] for details.
533#[derive(Debug, Clone)]
534pub struct String {
535    #[allow(dead_code)]
536    data: CString,
537    // The FFI API needs a pointer to a C string.
538    pointer: *const c_void,
539}
540
541unsafe impl Send for String {}
542unsafe impl Sync for String {}
543
544impl String {
545    pub fn new<T: Into<Vec<u8>>>(data: T) -> Self {
546        let data = CString::new(data).unwrap();
547        let pointer = data.as_ptr() as _;
548
549        String { data, pointer }
550    }
551}
552
553impl ArgDataMethods for String {
554    fn type_(&self) -> DataType {
555        DataType::String
556    }
557
558    fn len(&self) -> usize {
559        1
560    }
561
562    fn as_c_ptr(&self) -> *const c_void {
563        &self.pointer as *const *const c_void as _
564    }
565}
566
567nsi_data_array_def!(f32, F32Slice, DataType::F32);
568nsi_data_array_def!(f64, F64Slice, DataType::F64);
569nsi_data_array_def!(i32, I32Slice, DataType::I32);
570nsi_data_array_def!(i64, I64Slice, DataType::I64);
571nsi_tuple_data_array_def!(f32, ColorSlice, DataType::Color, 3);
572nsi_tuple_data_array_def!(f32, PointSlice, DataType::Point, 3);
573nsi_tuple_data_array_def!(f32, VectorSlice, DataType::Vector, 3);
574nsi_tuple_data_array_def!(f32, NormalSlice, DataType::Normal, 3);
575nsi_tuple_data_array_def!(f32, MatrixF32Slice, DataType::MatrixF32, 16);
576nsi_tuple_data_array_def!(f64, MatrixF64Slice, DataType::MatrixF64, 16);
577
578/// Slice of weighted (rational) homogeneous 4-component f32 control points
579/// — backing for [`point4_f32_slice!`][crate::point4_f32_slice] (NURBS
580/// rational positions `Pw`, RGBA-style colour-with-alpha attributes, etc.).
581///
582/// On the wire this is a flat `NSITypeFloat` slice — the renderer infers
583/// the 4-component grouping from the attribute name (`Pw`). The wrapper
584/// exists so callers can keep the natural `&[[f32; 4]]` shape in Rust
585/// while the FFI sees `4 * N` flat floats.
586#[derive(Debug, Clone, PartialEq)]
587pub struct Point4F32Slice<'a> {
588    data: &'a [[f32; 4]],
589}
590
591impl<'a> Point4F32Slice<'a> {
592    pub fn new(data: &'a [[f32; 4]]) -> Self {
593        Self { data }
594    }
595}
596
597impl<'a> ArgDataMethods for Point4F32Slice<'a> {
598    fn type_(&self) -> DataType {
599        DataType::F32
600    }
601
602    /// Total flat `f32` count = `4 * number-of-points`. With the
603    /// default `arraylength = 1` the renderer receives that many
604    /// scalar floats and groups them by attribute semantics.
605    fn len(&self) -> usize {
606        self.data.len() * 4
607    }
608
609    fn as_c_ptr(&self) -> *const c_void {
610        self.data.as_ptr() as _
611    }
612}
613
614/// See [`ArgData`] for details.
615#[derive(Debug, Clone)]
616pub struct ReferenceSlice<'a> {
617    data: Vec<*const c_void>,
618    _marker: PhantomData<&'a ()>,
619}
620
621unsafe impl Send for ReferenceSlice<'static> {}
622unsafe impl Sync for ReferenceSlice<'static> {}
623
624impl<'a> ReferenceSlice<'a> {
625    pub fn new<T>(data: &'a [&'a T]) -> Self {
626        debug_assert_eq!(0, data.len() % DataType::Reference.elemensize());
627
628        Self {
629            data: data.iter().map(|r| r as *const _ as _).collect(),
630            _marker: PhantomData,
631        }
632    }
633}
634
635impl<'a> ArgDataMethods for ReferenceSlice<'a> {
636    fn type_(&self) -> DataType {
637        DataType::Reference
638    }
639
640    fn len(&self) -> usize {
641        self.data.len() / DataType::Reference.elemensize()
642    }
643
644    fn as_c_ptr(&self) -> *const c_void {
645        self.data.as_ptr() as _
646    }
647}
648
649/// See [`ArgData`] for details.
650#[derive(Debug, Clone)]
651pub struct StringSlice {
652    #[allow(dead_code)]
653    data: Vec<CString>,
654    pointer: Vec<*const c_void>,
655}
656
657unsafe impl Send for StringSlice {}
658unsafe impl Sync for StringSlice {}
659
660impl StringSlice {
661    pub fn new<T: Into<Vec<u8>> + Copy>(data: &[T]) -> Self {
662        let data = data
663            .iter()
664            .map(|s| CString::new(*s).unwrap())
665            .collect::<Vec<_>>();
666        let pointer = data.iter().map(|s| s.as_ptr() as _).collect();
667
668        StringSlice { data, pointer }
669    }
670}
671
672impl ArgDataMethods for StringSlice {
673    fn type_(&self) -> DataType {
674        DataType::String
675    }
676
677    fn len(&self) -> usize {
678        self.pointer.len()
679    }
680
681    fn as_c_ptr(&self) -> *const c_void {
682        self.pointer.as_ptr() as _
683    }
684}
685
686nsi_tuple_data_def!(f32, 3, Color, DataType::Color);
687nsi_tuple_data_def!(f32, 3, Point, DataType::Point);
688nsi_tuple_data_def!(f32, 3, Vector, DataType::Vector);
689nsi_tuple_data_def!(f32, 3, Normal, DataType::Normal);
690nsi_tuple_data_def!(f32, 16, MatrixF32, DataType::MatrixF32);
691nsi_tuple_data_def!(f64, 16, MatrixF64, DataType::MatrixF64);
692
693/// Identifies an [`Arg`]’s data type.
694#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
695#[repr(i32)]
696pub(crate) enum DataType {
697    /// A single [`f32`] value.
698    F32 = NSIType::F32 as _,
699    /// A single [`f64`] value.
700    F64 = NSIType::F64 as _,
701    /// Single [`i32`] value.
702    I32 = NSIType::I32 as _,
703    /// Single [`i64`] value.
704    I64 = NSIType::I64 as _,
705    /// A [`String`].
706    String = NSIType::String as _,
707    /// Color, given as three [`f32`] values,
708    /// usually in the range `0..1`. Red would e.g. be `[1.0, 0.0,
709    /// 0.0]. Assumed to be in a linear color space.`
710    Color = NSIType::Color as _,
711    /// Point, given as three [`f32`] values.
712    Point = NSIType::Point as _,
713    /// Vector, given as three [`f32`] values.
714    Vector = NSIType::Vector as _,
715    /// Normal vector, given as three [`f32`] values.
716    Normal = NSIType::Normal as _,
717    /// Transformation matrix, given as 16 [`f32`] values.
718    MatrixF32 = NSIType::MatrixF32 as _,
719    /// Transformation matrix, given as 16 [`f64`] values.
720    MatrixF64 = NSIType::MatrixF64 as _,
721    /// Raw (`*const T`) pointer.
722    Reference = NSIType::Pointer as _,
723}
724
725impl DataType {
726    /// Returns the number of components of the resp. type.
727    #[inline]
728    pub(crate) fn elemensize(&self) -> usize {
729        match self {
730            DataType::F32 => 1,
731            DataType::F64 => 1,
732            DataType::I32 => 1,
733            DataType::I64 => 1,
734            DataType::String => 1,
735            DataType::Color => 3,
736            DataType::Point => 3,
737            DataType::Vector => 3,
738            DataType::Normal => 3,
739            DataType::MatrixF32 => 16,
740            DataType::MatrixF64 => 16,
741            DataType::Reference => 1,
742        }
743    }
744}
745
746/// Create a [`F32`] argument.
747///
748/// Name accepts a string literal (escape hatch) or a typed
749/// [`Attribute<f32>`](crate::Attribute) / [`Parameter<f32>`](crate::Parameter)
750/// constant (compile-time type-checked).
751#[macro_export]
752macro_rules! f32 {
753    ($name: literal, $value: expr) => {
754        nsi::Arg::new($name, nsi::ArgData::from(nsi::F32::new($value)))
755    };
756    ($name: path, $value: expr) => {{
757        const __ATTR_CHECK: $crate::Attribute<f32> = $name;
758        nsi::Arg::new(
759            __ATTR_CHECK.name(),
760            nsi::ArgData::from(nsi::F32::new($value)),
761        )
762    }};
763}
764
765/// Create a [`F32Slice`] array argument.
766///
767/// Name accepts a string literal or a typed
768/// [`Attribute<[f32]>`](crate::Attribute) constant.
769#[macro_export]
770macro_rules! f32_slice {
771    ($name: literal, $value: expr) => {
772        nsi::Arg::new($name, nsi::ArgData::from(nsi::F32Slice::new($value)))
773    };
774    ($name: path, $value: expr) => {{
775        const __ATTR_CHECK: $crate::Attribute<[f32]> = $name;
776        nsi::Arg::new(
777            __ATTR_CHECK.name(),
778            nsi::ArgData::from(nsi::F32Slice::new($value)),
779        )
780    }};
781}
782
783/// Create a [`F64`] precision argument.
784///
785/// Name accepts a string literal or a typed
786/// [`Attribute<f64>`](crate::Attribute) constant.
787#[macro_export]
788macro_rules! f64 {
789    ($name: literal, $value: expr) => {
790        nsi::Arg::new($name, nsi::ArgData::from(nsi::F64::new($value)))
791    };
792    ($name: path, $value: expr) => {{
793        const __ATTR_CHECK: $crate::Attribute<f64> = $name;
794        nsi::Arg::new(
795            __ATTR_CHECK.name(),
796            nsi::ArgData::from(nsi::F64::new($value)),
797        )
798    }};
799}
800
801/// Create a [`F64Slice`] precision array argument.
802///
803/// Name accepts a string literal or a typed
804/// [`Attribute<[f64]>`](crate::Attribute) constant.
805#[macro_export]
806macro_rules! f64_slice {
807    ($name: literal, $value: expr) => {
808        nsi::Arg::new($name, nsi::ArgData::from(nsi::F64Slice::new($value)))
809    };
810    ($name: path, $value: expr) => {{
811        const __ATTR_CHECK: $crate::Attribute<[f64]> = $name;
812        nsi::Arg::new(
813            __ATTR_CHECK.name(),
814            nsi::ArgData::from(nsi::F64Slice::new($value)),
815        )
816    }};
817}
818
819/// Create a [`I32`] argument.
820///
821/// Name accepts a string literal or a typed
822/// [`Attribute<i32>`](crate::Attribute) constant.
823#[macro_export]
824macro_rules! i32 {
825    ($name: literal, $value: expr) => {
826        nsi::Arg::new($name, nsi::ArgData::from(nsi::I32::new($value)))
827    };
828    ($name: path, $value: expr) => {{
829        const __ATTR_CHECK: $crate::Attribute<i32> = $name;
830        nsi::Arg::new(
831            __ATTR_CHECK.name(),
832            nsi::ArgData::from(nsi::I32::new($value)),
833        )
834    }};
835}
836
837/// Create a [`I32Slice`] array argument.
838///
839/// Name accepts a string literal or a typed
840/// [`Attribute<[i32]>`](crate::Attribute) constant.
841#[macro_export]
842macro_rules! i32_slice {
843    ($name: literal, $value: expr) => {
844        nsi::Arg::new($name, nsi::ArgData::from(nsi::I32Slice::new($value)))
845    };
846    ($name: path, $value: expr) => {{
847        const __ATTR_CHECK: $crate::Attribute<[i32]> = $name;
848        nsi::Arg::new(
849            __ATTR_CHECK.name(),
850            nsi::ArgData::from(nsi::I32Slice::new($value)),
851        )
852    }};
853}
854
855/// Create a [`I64`] argument.
856///
857/// Name accepts a string literal or a typed
858/// [`Attribute<i64>`](crate::Attribute) constant.
859#[macro_export]
860macro_rules! i64 {
861    ($name: literal, $value: expr) => {
862        nsi::Arg::new($name, nsi::ArgData::from(nsi::I64::new($value)))
863    };
864    ($name: path, $value: expr) => {{
865        const __ATTR_CHECK: $crate::Attribute<i64> = $name;
866        nsi::Arg::new(
867            __ATTR_CHECK.name(),
868            nsi::ArgData::from(nsi::I64::new($value)),
869        )
870    }};
871}
872
873/// Create a [`I64Slice`] array argument.
874///
875/// Name accepts a string literal or a typed
876/// [`Attribute<[i64]>`](crate::Attribute) constant.
877#[macro_export]
878macro_rules! i64_slice {
879    ($name: literal, $value: expr) => {
880        nsi::Arg::new($name, nsi::ArgData::from(nsi::I64Slice::new($value)))
881    };
882    ($name: path, $value: expr) => {{
883        const __ATTR_CHECK: $crate::Attribute<[i64]> = $name;
884        nsi::Arg::new(
885            __ATTR_CHECK.name(),
886            nsi::ArgData::from(nsi::I64Slice::new($value)),
887        )
888    }};
889}
890
891/// Create a [`Color`] argument.
892///
893/// Name accepts a string literal or a typed
894/// [`Attribute<Color3F32>`](crate::Attribute) constant.
895#[macro_export]
896macro_rules! color {
897    ($name: literal, $value: expr) => {
898        nsi::Arg::new($name, nsi::ArgData::from(nsi::Color::new($value)))
899    };
900    ($name: path, $value: expr) => {{
901        const __ATTR_CHECK: $crate::Attribute<$crate::Color3F32> = $name;
902        nsi::Arg::new(
903            __ATTR_CHECK.name(),
904            nsi::ArgData::from(nsi::Color::new($value)),
905        )
906    }};
907}
908
909/// Create a [`ColorSlice`] array argument.
910///
911/// Name accepts a string literal or a typed
912/// [`Attribute<[Color3F32]>`](crate::Attribute) constant.
913#[macro_export]
914macro_rules! color_slice {
915    ($name: literal, $value: expr) => {
916        nsi::Arg::new($name, nsi::ArgData::from(nsi::ColorSlice::new($value)))
917    };
918    ($name: path, $value: expr) => {{
919        const __ATTR_CHECK: $crate::Attribute<[$crate::Color3F32]> = $name;
920        nsi::Arg::new(
921            __ATTR_CHECK.name(),
922            nsi::ArgData::from(nsi::ColorSlice::new($value)),
923        )
924    }};
925}
926
927/// Create a [`Point`] argument.
928///
929/// Name accepts a string literal or a typed
930/// [`Attribute<Point3F32>`](crate::Attribute) constant.
931#[macro_export]
932macro_rules! point {
933    ($name: literal, $value: expr) => {
934        nsi::Arg::new($name, nsi::ArgData::from(nsi::Point::new($value)))
935    };
936    ($name: path, $value: expr) => {{
937        const __ATTR_CHECK: $crate::Attribute<$crate::Point3F32> = $name;
938        nsi::Arg::new(
939            __ATTR_CHECK.name(),
940            nsi::ArgData::from(nsi::Point::new($value)),
941        )
942    }};
943}
944
945/// Create a [`PointSlice`] array argument.
946///
947/// First argument may be either:
948/// * a string literal (escape hatch — no static check), or
949/// * a typed name constant of type [`Attribute<[Point3F32]>`](crate::Attribute) /
950///   [`Parameter<[Point3F32]>`](crate::Parameter) (compile-time type-checked).
951#[macro_export]
952macro_rules! point_slice {
953    // String-literal name -- legacy / escape hatch (no type check).
954    ($name: literal, $value: expr) => {
955        nsi::Arg::new($name, nsi::ArgData::from(nsi::PointSlice::new($value)))
956    };
957    // Typed Attribute<[Point3F32]> path -- compile-time type-checked.
958    ($name: path, $value: expr) => {{
959        const __ATTR_CHECK: $crate::Attribute<[$crate::Point3F32]> = $name;
960        nsi::Arg::new(
961            __ATTR_CHECK.name(),
962            nsi::ArgData::from(nsi::PointSlice::new($value)),
963        )
964    }};
965}
966
967/// Create a slice-of-4-component-f32-points argument.
968///
969/// Wraps a `&[[f32; 4]]` (e.g. weighted homogeneous control points
970/// `Pw` for NURBS, RGBA colour-with-alpha attributes, or any other
971/// 4-float-per-element vertex datum) and sends it as a **flat**
972/// `NSITypeFloat` slice — the renderer groups the floats into
973/// 4-tuples by attribute name, analogous to how `uknot`/`vknot` are
974/// flat float slices.
975///
976/// Name accepts:
977/// * a string literal (escape hatch — no static check), or
978/// * a typed name constant of type
979///   [`Attribute<[Point4F32]>`](crate::Attribute) / [`Parameter<[Point4F32]>`](crate::Parameter)
980///   (compile-time type-checked, e.g. [`WEIGHTED_POSITION`](crate::WEIGHTED_POSITION)).
981#[macro_export]
982macro_rules! point4_f32_slice {
983    // String-literal name — legacy / escape hatch (no type check).
984    ($name: literal, $value: expr) => {
985        nsi::Arg::new(
986            $name,
987            nsi::ArgData::from(nsi::Point4F32Slice::new($value)),
988        )
989    };
990    // Typed Attribute<[Point4F32]> path — compile-time type-checked.
991    ($name: path, $value: expr) => {{
992        const __ATTR_CHECK: $crate::Attribute<[$crate::Point4F32]> = $name;
993        nsi::Arg::new(
994            __ATTR_CHECK.name(),
995            nsi::ArgData::from(nsi::Point4F32Slice::new($value)),
996        )
997    }};
998}
999
1000/// Create a [`Vector`] argument.
1001///
1002/// Name accepts a string literal or a typed
1003/// [`Attribute<Vector3F32>`](crate::Attribute) constant.
1004#[macro_export]
1005macro_rules! vector {
1006    ($name: literal, $value: expr) => {
1007        nsi::Arg::new($name, nsi::ArgData::from(nsi::Vector::new($value)))
1008    };
1009    ($name: path, $value: expr) => {{
1010        const __ATTR_CHECK: $crate::Attribute<$crate::Vector3F32> = $name;
1011        nsi::Arg::new(
1012            __ATTR_CHECK.name(),
1013            nsi::ArgData::from(nsi::Vector::new($value)),
1014        )
1015    }};
1016}
1017
1018/// Create a [`VectorSlice`] array argument.
1019///
1020/// Name accepts a string literal or a typed
1021/// [`Attribute<[Vector3F32]>`](crate::Attribute) constant.
1022#[macro_export]
1023macro_rules! vector_slice {
1024    ($name: literal, $value: expr) => {
1025        nsi::Arg::new($name, nsi::ArgData::from(nsi::VectorSlice::new($value)))
1026    };
1027    ($name: path, $value: expr) => {{
1028        const __ATTR_CHECK: $crate::Attribute<[$crate::Vector3F32]> = $name;
1029        nsi::Arg::new(
1030            __ATTR_CHECK.name(),
1031            nsi::ArgData::from(nsi::VectorSlice::new($value)),
1032        )
1033    }};
1034}
1035
1036/// Create a [`Normal`] argument.
1037///
1038/// Name accepts a string literal or a typed
1039/// [`Attribute<Normal3F32>`](crate::Attribute) constant.
1040#[macro_export]
1041macro_rules! normal {
1042    ($name: literal, $value: expr) => {
1043        nsi::Arg::new($name, nsi::ArgData::from(nsi::Normal::new($value)))
1044    };
1045    ($name: path, $value: expr) => {{
1046        const __ATTR_CHECK: $crate::Attribute<$crate::Normal3F32> = $name;
1047        nsi::Arg::new(
1048            __ATTR_CHECK.name(),
1049            nsi::ArgData::from(nsi::Normal::new($value)),
1050        )
1051    }};
1052}
1053
1054/// Create a [`NormalSlice`] array argument.
1055///
1056/// Name accepts a string literal or a typed
1057/// [`Attribute<[Normal3F32]>`](crate::Attribute) constant.
1058#[macro_export]
1059macro_rules! normal_slice {
1060    ($name: literal, $value: expr) => {
1061        nsi::Arg::new($name, nsi::ArgData::from(nsi::NormalSlice::new($value)))
1062    };
1063    ($name: path, $value: expr) => {{
1064        const __ATTR_CHECK: $crate::Attribute<[$crate::Normal3F32]> = $name;
1065        nsi::Arg::new(
1066            __ATTR_CHECK.name(),
1067            nsi::ArgData::from(nsi::NormalSlice::new($value)),
1068        )
1069    }};
1070}
1071
1072/// Create a [`MatrixF32`] row-major, 4×4 transformation matrix argument.
1073/// The matrix is given as 16 [`f32`] values.
1074///
1075/// Name accepts a string literal or a typed
1076/// [`Attribute<Matrix4F32>`](crate::Attribute) constant.
1077#[macro_export]
1078macro_rules! matrix_f32 {
1079    ($name: literal, $value: expr) => {
1080        nsi::Arg::new($name, nsi::ArgData::from(nsi::MatrixF32::new($value)))
1081    };
1082    ($name: path, $value: expr) => {{
1083        const __ATTR_CHECK: $crate::Attribute<$crate::Matrix4F32> = $name;
1084        nsi::Arg::new(
1085            __ATTR_CHECK.name(),
1086            nsi::ArgData::from(nsi::MatrixF32::new($value)),
1087        )
1088    }};
1089}
1090
1091/// Create a [`MatrixF32Slice`] row-major, 4×4 transformation matrices argument.
1092/// Each matrix is given as 16 [`f32`] values.
1093///
1094/// Name accepts a string literal or a typed
1095/// [`Attribute<[Matrix4F32]>`](crate::Attribute) constant.
1096#[macro_export]
1097macro_rules! matrix_f32_slice {
1098    ($name: literal, $value: expr) => {
1099        nsi::Arg::new(
1100            $name,
1101            nsi::ArgData::from(nsi::MatrixF32Slice::new($value)),
1102        )
1103    };
1104    ($name: path, $value: expr) => {{
1105        const __ATTR_CHECK: $crate::Attribute<[$crate::Matrix4F32]> = $name;
1106        nsi::Arg::new(
1107            __ATTR_CHECK.name(),
1108            nsi::ArgData::from(nsi::MatrixF32Slice::new($value)),
1109        )
1110    }};
1111}
1112
1113/// Create a [`MatrixF64`] row-major, 4×4 transformation matrix argument.
1114/// The matrix is given as 16 [`f64`] values.
1115///
1116/// # Examples
1117///
1118/// ```
1119/// # use nsi_ffi_wrap as nsi;
1120/// # let ctx = nsi::Context::new(None).unwrap();
1121/// // Setup a transform node.
1122/// ctx.create("xform", nsi::TRANSFORM, None);
1123/// ctx.connect("xform", None, nsi::ROOT, "objects", None);
1124///
1125/// // Translate 5 units along z-axis.
1126/// ctx.set_attribute(
1127///     "xform",
1128///     &[nsi::matrix_f64!(
1129///         "transformationmatrix",
1130///         &[
1131///             1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 5., 1.,
1132///         ]
1133///     )],
1134/// );
1135/// ```
1136#[macro_export]
1137macro_rules! matrix_f64 {
1138    ($name: literal, $value: expr) => {
1139        nsi::Arg::new($name, nsi::ArgData::from(nsi::MatrixF64::new($value)))
1140    };
1141    ($name: path, $value: expr) => {{
1142        const __ATTR_CHECK: $crate::Attribute<$crate::Matrix4F64> = $name;
1143        nsi::Arg::new(
1144            __ATTR_CHECK.name(),
1145            nsi::ArgData::from(nsi::MatrixF64::new($value)),
1146        )
1147    }};
1148}
1149
1150/// Create a [`MatrixF64Slice`] row-major, 4×4 transformation matrices argument.
1151/// Each matrix is given as 16 [`f64`] values.
1152///
1153/// Name accepts a string literal or a typed
1154/// [`Attribute<[Matrix4F64]>`](crate::Attribute) constant.
1155#[macro_export]
1156macro_rules! matrix_f64_slice {
1157    ($name: literal, $value: expr) => {
1158        nsi::Arg::new(
1159            $name,
1160            nsi::ArgData::from(nsi::MatrixF64Slice::new($value)),
1161        )
1162    };
1163    ($name: path, $value: expr) => {{
1164        const __ATTR_CHECK: $crate::Attribute<[$crate::Matrix4F64]> = $name;
1165        nsi::Arg::new(
1166            __ATTR_CHECK.name(),
1167            nsi::ArgData::from(nsi::MatrixF64Slice::new($value)),
1168        )
1169    }};
1170}
1171
1172/// Create a [`String`] argument.
1173///
1174/// # Examples
1175///
1176/// ```
1177/// # use nsi_ffi_wrap as nsi;
1178/// // Create rendering context.
1179/// let ctx =
1180///     nsi::Context::new(Some(&[nsi::string!("streamfilename", "stdout")]))
1181///         .expect("Could not create NSI context.");
1182/// ```
1183#[macro_export]
1184macro_rules! string {
1185    ($name: literal, $value: expr) => {
1186        nsi::Arg::new($name, nsi::ArgData::from(nsi::String::new($value)))
1187    };
1188    ($name: path, $value: expr) => {{
1189        const __ATTR_CHECK: $crate::Attribute<&'static str> = $name;
1190        nsi::Arg::new(
1191            __ATTR_CHECK.name(),
1192            nsi::ArgData::from(nsi::String::new($value)),
1193        )
1194    }};
1195}
1196
1197/// Create a [`String`] array argument.
1198///
1199/// # Examples
1200///
1201/// ```
1202/// # use nsi_ffi_wrap as nsi;
1203/// # let ctx = nsi::Context::new(None).unwrap();
1204/// // One of these is not an actor:
1205/// ctx.set_attribute(
1206///     "dummy",
1207///     &[nsi::string_slice!(
1208///         "actors",
1209///         &["Klaus Kinski", "Giorgio Moroder", "Rainer Brandt"]
1210///     )],
1211/// );
1212/// ```
1213#[macro_export]
1214macro_rules! string_slice {
1215    ($name: literal, $value: expr) => {
1216        nsi::Arg::new($name, nsi::ArgData::from(nsi::StringSlice::new($value)))
1217    };
1218    ($name: path, $value: expr) => {{
1219        const __ATTR_CHECK: $crate::Attribute<[&'static str]> = $name;
1220        nsi::Arg::new(
1221            __ATTR_CHECK.name(),
1222            nsi::ArgData::from(nsi::StringSlice::new($value)),
1223        )
1224    }};
1225}
1226
1227/// Create a [`Reference`] argument.
1228///
1229/// This macro accepts:
1230/// - `&Box<T>` - ReferenceSlice to boxed data
1231/// - `&Arc<T>` - ReferenceSlice to Arc'd data  
1232/// - `&Pin<Box<T>>` - ReferenceSlice to pinned boxes
1233///
1234/// For other types with stable addresses, use `reference_stable!` instead.
1235#[macro_export]
1236macro_rules! reference {
1237    ($name: tt, $value: expr) => {
1238        nsi::Arg::new($name, nsi::ArgData::from(nsi::Reference::new($value)))
1239    };
1240}
1241
1242/// Create a [`Reference`] argument from data with a stable address.
1243///
1244/// # Safety
1245/// You must ensure the data has a stable address (heap allocated, static, etc.)
1246/// and won't be moved for the lifetime of the reference.
1247#[macro_export]
1248macro_rules! reference_stable {
1249    ($name: tt, $value: expr) => {
1250        nsi::Arg::new(
1251            $name,
1252            nsi::ArgData::from(unsafe { nsi::Reference::from_stable($value) }),
1253        )
1254    };
1255}
1256
1257/// Create a [`Reference`] array argument.
1258#[macro_export]
1259macro_rules! reference_slice {
1260    ($name: tt, $value: expr) => {
1261        nsi::Arg::new(
1262            $name,
1263            nsi::ArgData::from(nsi::ReferenceSlice::new($value)),
1264        )
1265    };
1266}
1267
1268/// Create a [`Callback`] argument.
1269#[macro_export]
1270macro_rules! callback {
1271    ($name: tt, $value: expr) => {
1272        nsi::Arg::new($name, nsi::ArgData::from(nsi::Callback::new($value)))
1273    };
1274}