Skip to main content

nsi_trait/
nsi_trait.rs

1//! Core ɴsɪ trait, parameter abstraction, and FFI-compatible types.
2
3use bitflags::bitflags;
4
5// ─── Type Enum ──────────────────────────────────────────────────────────────
6
7/// NSI data type discriminant, binary-compatible with `NSIType_t` from `nsi.h`.
8///
9/// Values use bit flags from the C header -- they are NOT sequential.
10#[repr(i32)]
11#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
12pub enum Type {
13    Invalid = 0,
14    F32 = 1,
15    F64 = 0x11,
16    I32 = 2,
17    I64 = 0x12,
18    String = 3,
19    Color = 4,
20    Point = 5,
21    Vector = 6,
22    Normal = 7,
23    MatrixF32 = 8,
24    MatrixF64 = 0x18,
25    /// Called "Pointer" in the C API; renamed for clarity.
26    Reference = 9,
27}
28
29// ─── Flags ──────────────────────────────────────────────────────────────────
30
31bitflags! {
32    /// Parameter flags matching the C API constants.
33    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34    pub struct Flags: i32 {
35        const IS_ARRAY = 1;
36        const PER_FACE = 2;
37        const PER_VERTEX = 4;
38        const INTERPOLATE_LINEAR = 8;
39    }
40}
41
42// ─── FfiParam ───────────────────────────────────────────────────────────────
43
44/// C-compatible parameter struct, layout-identical to `NSIParam_t`.
45#[repr(C)]
46#[derive(Debug, Clone, Copy)]
47pub struct FfiParam {
48    pub name: *const core::ffi::c_char,
49    pub data: *const core::ffi::c_void,
50    pub type_: core::ffi::c_int,
51    pub arraylength: core::ffi::c_int,
52    pub count: usize,
53    pub flags: core::ffi::c_int,
54}
55
56// SAFETY: FfiParam is a POD struct with raw pointers that are only
57// dereferenced by the C API on the same thread that calls it.
58unsafe impl Send for FfiParam {}
59unsafe impl Sync for FfiParam {}
60
61// ─── Name Type ──────────────────────────────────────────────────────────────
62
63/// Interned string type when the `ustr` feature is enabled, otherwise `String`.
64#[cfg(feature = "ustr")]
65pub type Name = ustr::Ustr;
66
67/// Plain `String` when the `ustr` feature is not enabled.
68#[cfg(not(feature = "ustr"))]
69pub type Name = String;
70
71// ─── Parameter Trait ────────────────────────────────────────────────────────
72
73/// A single ɴsɪ parameter (name + typed data).
74///
75/// Lifetime-free -- implementors own or borrow their data as they see fit.
76/// Mirrors the published crate's internal `ArgDataMethods` + `Arg` metadata.
77pub trait ParamValue {
78    /// Parameter name.
79    fn name(&self) -> &str;
80
81    /// NSI type discriminant.
82    fn type_tag(&self) -> Type;
83
84    /// Number of data elements.
85    fn len(&self) -> usize;
86
87    /// Whether the parameter carries zero elements.
88    fn is_empty(&self) -> bool {
89        self.len() == 0
90    }
91
92    /// Array length (for array-of-arrays; default 1).
93    fn array_length(&self) -> usize {
94        1
95    }
96
97    /// Parameter flags (PerFace, PerVertex, IsArray, InterpolateLinear).
98    fn flags(&self) -> i32 {
99        0
100    }
101
102    /// FFI fast-path: return a C-compatible param struct if the layout
103    /// supports it.
104    ///
105    /// Returns `Some(FfiParam)` when the data is already in C-compatible
106    /// layout. Returns `None` when the consumer must construct a temporary
107    /// `FfiParam`.
108    ///
109    /// # Safety
110    ///
111    /// The returned `FfiParam`'s pointers are valid only while `self` is
112    /// alive.
113    fn as_c_param(&self) -> Option<FfiParam>;
114}
115
116// ─── Action Enum ────────────────────────────────────────────────────────────
117
118/// Actions for render control.
119///
120/// These control the rendering process after it has been started.
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub enum Action {
123    /// Start rendering. Returns immediately, rendering proceeds in parallel.
124    Start,
125    /// Wait for rendering to complete. Blocks until finished or stopped.
126    Wait,
127    /// Synchronize scene changes for interactive renders.
128    Synchronize,
129    /// Temporarily pause the render process.
130    Suspend,
131    /// Continue a previously suspended render.
132    Resume,
133    /// Stop rendering without destroying the scene.
134    Stop,
135}
136
137impl Action {
138    /// Returns the string identifier used by the C API.
139    #[inline]
140    pub const fn as_str(&self) -> &'static str {
141        match self {
142            Self::Start => "start",
143            Self::Wait => "wait",
144            Self::Synchronize => "synchronize",
145            Self::Suspend => "suspend",
146            Self::Resume => "resume",
147            Self::Stop => "stop",
148        }
149    }
150
151    /// Parse an action from its string identifier.
152    pub fn from_name(s: &str) -> Option<Self> {
153        Some(match s {
154            "start" => Self::Start,
155            "wait" => Self::Wait,
156            "synchronize" => Self::Synchronize,
157            "suspend" => Self::Suspend,
158            "resume" => Self::Resume,
159            "stop" => Self::Stop,
160            _ => return None,
161        })
162    }
163}
164
165impl std::fmt::Display for Action {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        f.write_str(self.as_str())
168    }
169}
170
171// ─── Nsi Trait ──────────────────────────────────────────────────────────────
172
173/// Core ɴsɪ interface trait.
174///
175/// `Self` IS the rendering context. There is no separate handle type --
176/// construction is implementation-specific, destruction is via `Drop`.
177///
178/// # Generic Associated Type
179///
180/// `Arg<'call>` uses a GAT with a transient borrow lifetime (`'call`).
181/// The context-bound lifetime (for ReferenceSlice/Callbacks that must outlive
182/// the context) is baked into the implementor's concrete `Arg` type.
183///
184/// # Thread Safety
185///
186/// All methods take `&self`. Implementors use interior mutability where
187/// needed.
188///
189/// # String-Based Node Types
190///
191/// Methods accept `&str` for node types to allow custom types beyond the
192/// standard specification. Use constants like [`MESH`](crate::MESH),
193/// [`SHADER`](crate::SHADER), etc. for standard types.
194pub trait Nsi: Send + Sync {
195    /// Argument type -- each implementor picks its own.
196    ///
197    /// `'call` is the transient borrow lifetime (data copied by C side).
198    /// The context-bound lifetime (for ReferenceSlice/Callbacks) is baked
199    /// into the implementor's concrete Arg type.
200    type Arg<'call>: ParamValue
201    where
202        Self: 'call;
203
204    /// Error type for fallible operations.
205    type Error: std::error::Error + Send + Sync + 'static;
206
207    /// Create a new node in the scene graph.
208    fn create(
209        &self,
210        handle: &str,
211        node_type: &str,
212        args: Option<&[Self::Arg<'_>]>,
213    ) -> Result<(), Self::Error>;
214
215    /// Delete a node from the scene graph.
216    fn delete(
217        &self,
218        handle: &str,
219        args: Option<&[Self::Arg<'_>]>,
220    ) -> Result<(), Self::Error>;
221
222    /// Set attributes on a node.
223    fn set_attribute(
224        &self,
225        handle: &str,
226        args: &[Self::Arg<'_>],
227    ) -> Result<(), Self::Error>;
228
229    /// Set attributes on a node at a specific time (for motion blur).
230    fn set_attribute_at_time(
231        &self,
232        handle: &str,
233        time: f64,
234        args: &[Self::Arg<'_>],
235    ) -> Result<(), Self::Error>;
236
237    /// Delete an attribute from a node.
238    fn delete_attribute(
239        &self,
240        handle: &str,
241        name: &str,
242    ) -> Result<(), Self::Error>;
243
244    /// Connect two nodes in the scene graph.
245    fn connect(
246        &self,
247        from: &str,
248        from_attr: Option<&str>,
249        to: &str,
250        to_attr: &str,
251        args: Option<&[Self::Arg<'_>]>,
252    ) -> Result<(), Self::Error>;
253
254    /// Disconnect two nodes in the scene graph.
255    fn disconnect(
256        &self,
257        from: &str,
258        from_attr: Option<&str>,
259        to: &str,
260        to_attr: &str,
261    ) -> Result<(), Self::Error>;
262
263    /// Evaluate procedural nodes or Lua scripts.
264    fn evaluate(&self, args: &[Self::Arg<'_>]) -> Result<(), Self::Error>;
265
266    /// Control the rendering process.
267    fn render_control(
268        &self,
269        action: Action,
270        args: Option<&[Self::Arg<'_>]>,
271    ) -> Result<(), Self::Error>;
272}
273
274// ─── Tests ──────────────────────────────────────────────────────────────────
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn action_round_trip() {
282        let actions = [
283            Action::Start,
284            Action::Wait,
285            Action::Synchronize,
286            Action::Suspend,
287            Action::Resume,
288            Action::Stop,
289        ];
290
291        for action in actions {
292            let s = action.as_str();
293            let parsed = Action::from_name(s).expect("should parse");
294            assert_eq!(action, parsed);
295        }
296    }
297
298    #[test]
299    fn type_values_match_c_header() {
300        assert_eq!(Type::Invalid as i32, 0);
301        assert_eq!(Type::F32 as i32, 1);
302        assert_eq!(Type::F64 as i32, 0x11);
303        assert_eq!(Type::I32 as i32, 2);
304        assert_eq!(Type::I64 as i32, 0x12);
305        assert_eq!(Type::String as i32, 3);
306        assert_eq!(Type::Color as i32, 4);
307        assert_eq!(Type::Point as i32, 5);
308        assert_eq!(Type::Vector as i32, 6);
309        assert_eq!(Type::Normal as i32, 7);
310        assert_eq!(Type::MatrixF32 as i32, 8);
311        assert_eq!(Type::MatrixF64 as i32, 0x18);
312        assert_eq!(Type::Reference as i32, 9);
313    }
314
315    #[test]
316    fn flags_combine() {
317        let f = Flags::PER_VERTEX | Flags::IS_ARRAY;
318        assert!(f.contains(Flags::PER_VERTEX));
319        assert!(f.contains(Flags::IS_ARRAY));
320        assert!(!f.contains(Flags::PER_FACE));
321        // 4 | 1.
322        assert_eq!(f.bits(), 5);
323    }
324
325    #[test]
326    fn ffi_param_layout() {
327        // Verify field count / size is reasonable for a 6-field repr(C) struct.
328        // Exact size depends on platform, but must be at least 6 * pointer-size
329        // on 64-bit or a mix of i32 + pointer on 32-bit.
330        assert!(std::mem::size_of::<FfiParam>() >= 6 * 4);
331    }
332}