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