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}