nsi_ffi_wrap/context.rs
1//! An ɴsɪ context.
2
3// Needed for the example dode to build.
4extern crate self as nsi;
5#[allow(unused_imports)]
6use crate::HashMap;
7use crate::*;
8use parking_lot::Mutex;
9use std::{
10 ffi::{CStr, c_char},
11 marker::PhantomData,
12 ops::Drop,
13 os::raw::{c_int, c_void},
14};
15use triomphe::Arc;
16use ustr::{Ustr, ustr};
17
18/// The actual context and a marker to hold on to callbacks
19/// (closures)/references passed via [`set_attribute()`] or the like.
20///
21/// We wrap this in an [`Arc`] in [`Context`] to make sure drop() is only
22/// called when the last clone ceases existing.
23struct InnerContext<'a> {
24 context: NSIContext,
25 /// The renderer this context is talking to.
26 ///
27 /// **Held here rather than looked up.** The renderer used to be a
28 /// process-wide static, so a process had one for its whole life.
29 /// Carrying it means two contexts in one process can be two
30 /// different renderers, which is what `"renderer"` promises -- and
31 /// it means the library cannot be dropped while a context still
32 /// needs it to run `NSIEnd`.
33 api: std::sync::Arc<dyn FfiApi>,
34 /// The callbacks this context was handed, keyed by the node handle and
35 /// attribute name they were set on.
36 ///
37 /// A callback is a closure that has been turned into a raw pointer for
38 /// the renderer, so nothing else can free it. The context is the right
39 /// owner: it outlives every callback set on it, and its `Drop` runs
40 /// `NSIEnd`, after which the renderer can no longer reach them.
41 callbacks: Mutex<HashMap<(Ustr, Ustr), CallbackOwned>>,
42 /// Callbacks displaced by a later `set_attribute` on the same key.
43 ///
44 /// Not freed on the spot: a render may still be running with the old
45 /// pointer. They are reclaimed at the next `Stop`/`Wait` -- when no
46 /// render is in flight -- or when the context drops.
47 retired: Mutex<Vec<CallbackOwned>>,
48 // _marker needs to be invariant in 'a.
49 // See "Making a struct outlive a parameter given to a method of
50 // that struct": https://stackoverflow.com/questions/62374326/.
51 _marker: PhantomData<*mut &'a ()>,
52}
53
54/// The `"renderer"` argument's value, if the caller gave one.
55///
56/// Only a string is meaningful: a renderer is named, not measured. An
57/// argument by that name carrying anything else is ignored here, and
58/// then dropped from what is forwarded like any other -- so a caller
59/// who passes the wrong type gets the default renderer rather than a
60/// renderer that receives an argument it cannot read.
61fn requested_renderer(args: &ArgSlice<'_, '_>) -> Option<std::string::String> {
62 args.iter()
63 .find(|arg| crate::backend::RENDERER == arg.name.as_str())
64 .and_then(|arg| match &arg.data {
65 ArgData::String(value) => value.as_str(),
66 _ => None,
67 })
68 .map(|name| name.to_string())
69}
70
71/// A callback pointer the context owns, with the function that frees it.
72#[derive(Debug)]
73struct CallbackOwned {
74 data: *const c_void,
75 drop_fn: unsafe fn(*const c_void),
76}
77
78impl Drop for CallbackOwned {
79 fn drop(&mut self) {
80 // SAFETY: `data` came from the matching `CallbackPtr::to_ptr`, is
81 // reclaimed exactly once (this type is neither `Copy` nor `Clone`),
82 // and by construction the renderer can no longer reach it.
83 unsafe { (self.drop_fn)(self.data) }
84 }
85}
86
87// The context id alone identifies a context; the callback tables are
88// interior mutability and say nothing about identity.
89// By hand, because `dyn FfiApi` is not `Debug` and giving it that
90// bound would constrain every implementation for the sake of a
91// derive.
92impl<'a> core::fmt::Debug for InnerContext<'a> {
93 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94 f.debug_struct("InnerContext")
95 .field("context", &self.context)
96 .field("callbacks", &self.callbacks)
97 .field("retired", &self.retired)
98 .finish_non_exhaustive()
99 }
100}
101
102impl<'a> PartialEq for InnerContext<'a> {
103 fn eq(&self, other: &Self) -> bool {
104 self.context == other.context
105 }
106}
107impl<'a> Eq for InnerContext<'a> {}
108impl<'a> core::hash::Hash for InnerContext<'a> {
109 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
110 self.context.hash(state);
111 }
112}
113
114impl<'a> InnerContext<'a> {
115 fn new(context: NSIContext, api: std::sync::Arc<dyn FfiApi>) -> Self {
116 Self {
117 context,
118 api,
119 callbacks: Mutex::new(HashMap::new()),
120 retired: Mutex::new(Vec::new()),
121 _marker: PhantomData,
122 }
123 }
124
125 /// Takes ownership of every callback in `args`, keyed by `handle` and
126 /// the argument name. Anything it displaces is retired, not freed.
127 fn own_callbacks(&self, handle: &str, args: Option<&ArgSlice<'_, 'a>>) {
128 let Some(args) = args else { return };
129
130 let handle = ustr(handle);
131 let mut owned = self.callbacks.lock();
132
133 args.iter()
134 .filter_map(|arg| match &arg.data {
135 ArgData::Callback(callback) => Some((arg.name, callback)),
136 _ => None,
137 })
138 .for_each(|(name, callback)| {
139 if let Some(displaced) = owned.insert(
140 (handle, name),
141 CallbackOwned {
142 data: callback.data,
143 drop_fn: callback.drop_fn,
144 },
145 ) {
146 self.retired.lock().push(displaced);
147 }
148 });
149 }
150
151 /// Frees callbacks displaced by a later `set_attribute`.
152 ///
153 /// Only call this where no render can be using them -- after `Stop` or
154 /// `Wait` returns, or once `NSIEnd` has.
155 fn reclaim_retired(&self) {
156 self.retired.lock().clear();
157 }
158}
159
160// Guaranteed by the C API.
161unsafe impl<'a> Send for InnerContext<'a> {}
162unsafe impl<'a> Sync for InnerContext<'a> {}
163
164impl<'a> Drop for InnerContext<'a> {
165 #[inline]
166 fn drop(&mut self) {
167 // Order matters: the renderer may still reach a callback until
168 // `NSIEnd` returns, so free nothing before it does.
169 self.api.NSIEnd(self.context);
170 crate::forget(self.context);
171 self.callbacks.lock().clear();
172 self.retired.lock().clear();
173 }
174}
175
176/// # An ɴꜱɪ Context.
177///
178/// A `Context` is used to describe a scene to the renderer and request images
179/// to be rendered from it.
180///
181/// ## Safety
182/// A `Context` may be used in multiple threads at once.
183///
184/// ## Lifetime
185/// A `Context` can be used without worrying about its lifetime until you want
186/// to store it somewhere, e.g. in a struct.
187///
188/// The reason `Context` has an explicit lifetime is so that it can take
189/// [`Reference`]s and [`Callback`]s (closures). These must be valid until the
190/// context is dropped and this guarantee requires explicit lifetimes. When you
191/// use a context directly this is not an issue but when you want to reference
192/// it somewhere the same rules as with all references apply.
193///
194/// ## Memory & ownership: callbacks and references
195///
196/// A [`Callback`] is a closure turned into a raw pointer for the renderer,
197/// so it has to outlive the call that passes it. The **`Context` owns it**
198/// from that moment and frees it for you. There is nothing to manage, but
199/// two consequences are worth knowing:
200///
201/// * **A callback keeps its captures alive.** An output-driver write
202/// callback typically captures an `Arc` to your pixel buffer, so that
203/// buffer lives as long as the callback does. `Arc::try_unwrap` on
204/// something a live callback captured will therefore fail -- read it out
205/// under its lock instead.
206/// * **Re-setting an attribute retires the callback it displaces**, rather
207/// than freeing it on the spot: a render may still be running with the
208/// old pointer. Retired callbacks are freed at the next
209/// [`Action::Stop`] or [`Action::Wait`], and unconditionally when the
210/// context drops. So a long-lived context whose callbacks are swapped
211/// repeatedly -- switching output modes, say -- reclaims them each time
212/// it stops, and in the worst case holds one displaced set until then.
213///
214/// Everything is released when the `Context` drops, after `NSIEnd`, which
215/// is the first moment the renderer can no longer reach any of it.
216///
217/// A [`Reference`] is different: it points at data *you* own (the
218/// [`StableDeref`] bound means a `Box` or `Arc`), so the context never
219/// frees it and you must keep it alive for as long as the context.
220///
221/// ## Let every thread that owns a `Context` finish before the process ends
222///
223/// `NSIEnd` runs in [`Drop`]. Two ways to skip it, both of which leave the
224/// renderer mid-shutdown while libc tears the process down -- a `SIGSEGV`
225/// *after* a completely clean render:
226///
227/// 1. [`std::process::exit`] (or `abort`) runs no destructors at all, so a
228/// live `Context` never reaches `NSIEnd`. Return from `main` normally --
229/// with [`std::process::ExitCode`] if you need a specific status.
230/// 2. Not joining a thread that owns a `Context`. Signalling completion
231/// over a channel is not enough: the thread still has to drop the
232/// `Context` afterwards, and that is where `NSIEnd` happens. Keep the
233/// [`JoinHandle`](std::thread::JoinHandle) and join it.
234///
235/// `NSIEnd` itself is synchronous -- once it returns, 3Delight has no
236/// threads of its own left running. See `examples/concurrent_interactive`,
237/// which joins its render threads and needs no other precaution.
238///
239/// ## Further Reading
240/// See the [ɴꜱɪ documentation on context
241/// handling](https://nsi.readthedocs.io/en/latest/c-api.html#context-handling).
242#[derive(Clone, Debug, Hash, PartialEq, Eq)]
243pub struct Context<'a>(Arc<InnerContext<'a>>);
244
245unsafe impl<'a> Send for Context<'a> {}
246unsafe impl<'a> Sync for Context<'a> {}
247
248impl<'a> From<Context<'a>> for NSIContext {
249 #[inline]
250 fn from(context: Context<'a>) -> Self {
251 context.0.context
252 }
253}
254
255impl<'a> From<NSIContext> for Context<'a> {
256 /// **The default renderer**, because a bare handle does not say
257 /// which one made it. Adopting a handle from a renderer that is
258 /// not the default is not expressible here; make the [`Context`]
259 /// with [`Context::new`] and a `"renderer"` argument instead.
260 ///
261 /// # Panics
262 ///
263 /// If no renderer can be loaded at all. Every other path returns
264 /// an error, but this conversion has nowhere to put one.
265 #[inline]
266 fn from(context: NSIContext) -> Self {
267 let api = crate::renderer_of(context)
268 .unwrap_or_else(|error| panic!("{error}"));
269 Self(Arc::new(InnerContext::new(context, api)))
270 }
271}
272
273impl<'a> Context<'a> {
274 /// Creates an ɴsɪ context.
275 ///
276 /// Contexts may be used in multiple threads at once.
277 ///
278 /// # Examples
279 ///
280 /// ```
281 /// # use nsi_ffi_wrap as nsi;
282 /// // Create rendering context that dumps to stdout.
283 /// let ctx =
284 /// nsi::Context::new(Some(&[nsi::string!("streamfilename", "stdout")]))
285 /// .expect("Could not create ɴsɪ context.");
286 /// ```
287 /// # Choosing a renderer
288 ///
289 /// ɴsɪ is an interface and more than one renderer implements it.
290 /// A `"renderer"` argument says which:
291 ///
292 /// ```no_run
293 /// # use nsi_ffi_wrap as nsi;
294 /// let ctx = nsi::Context::new(Some(&[nsi::string!("renderer", "moonray")]));
295 /// ```
296 ///
297 /// A name this crate knows is looked for where that renderer
298 /// installs; anything else is taken as a library to load, so an
299 /// implementation released after this crate still works, as does
300 /// a path to a build of your own. Without the argument,
301 /// `$NSI_RENDERER` decides, and without that, 3Delight.
302 /// [`backend`](crate::backend) has the whole rule.
303 ///
304 /// The argument is **consumed here and never forwarded**: it tells
305 /// this crate which library to open, and no renderer would know
306 /// what to do with it.
307 ///
308 /// # Error
309 /// If this method fails for some reason, it returns [`None`].
310 #[inline]
311 pub fn new(args: Option<&ArgSlice<'_, 'a>>) -> Option<Self> {
312 // Read before anything is turned into C parameters, and
313 // dropped from what is, further down.
314 //
315 // Dropped *there* rather than by filtering `args` into a new
316 // slice, which is what a first version did. Both are correct:
317 // the filtered `Vec` was a local that outlived the `NSIBegin`
318 // call, and a renderer copies what it is handed during the
319 // call -- `nsi-intermediate`'s `OwnedData` owns every payload,
320 // and the one thing it does not copy, a `HostPointer`, it
321 // never dereferences. This way simply does not clone the
322 // caller's arguments, which allocates nothing and leaves no
323 // second `Arg` holding a duplicate of a callback pointer that
324 // stands for ownership of a leaked box.
325 let renderer = args.and_then(requested_renderer);
326
327 let api = match crate::renderer(renderer.as_deref()) {
328 Ok(api) => api,
329 Err(error) => {
330 // The one failure this crate cannot render through, so
331 // it is worth a line even though the signature can
332 // only say `None`.
333 eprintln!("nsi: {error}");
334 return None;
335 }
336 };
337
338 let (_, _, mut args_out) = to_c_param_vec(args);
339
340 // **Not forwarded.** `"renderer"` told this crate which
341 // library to open; no renderer declares it. `Ustr` interns, so
342 // equal names are one pointer and this compares addresses.
343 if renderer.is_some() {
344 let directive = ustr(crate::backend::RENDERER).as_char_ptr();
345 args_out.retain(|param| !std::ptr::eq(param.name, directive));
346 }
347
348 let fn_pointer: nsi_sys::NSIErrorHandler = Some(
349 error_handler
350 as extern "C" fn(*mut c_void, c_int, c_int, *const c_char),
351 );
352
353 if let Some(args) = args
354 && let Some(arg) =
355 args.iter().find(|arg| ustr("errorhandler") == arg.name)
356 {
357 // `as_c_ptr` already yields the address of the pointer, which
358 // is what ɴsɪ reads a `Pointer` value out of. Taking its address
359 // again here would be one indirection too many.
360 args_out.push(nsi_sys::NSIParam {
361 name: ustr("errorhandler").as_char_ptr(),
362 data: &fn_pointer as *const _ as _,
363 type_: NSIType::Pointer as _,
364 arraylength: 0,
365 count: 1,
366 flags: 0,
367 });
368 args_out.push(nsi_sys::NSIParam {
369 name: ustr("errorhandlerdata").as_char_ptr(),
370 data: arg.data.as_c_ptr(),
371 type_: NSIType::Pointer as _,
372 arraylength: 1,
373 count: 1,
374 flags: 0,
375 });
376 }
377
378 let context = api.NSIBegin(args_out.len() as _, args_out.as_ptr());
379
380 if 0 == context {
381 None
382 } else {
383 crate::remember(context, &api);
384 let inner = InnerContext::new(context, api);
385 // The error handler is a callback like any other; the context
386 // owns it under a reserved key, since it belongs to no node.
387 inner.own_callbacks(".context", args);
388 Some(Self(Arc::new(inner)))
389 }
390 }
391
392 /// Creates a new node.
393 ///
394 /// # Arguments
395 ///
396 /// * `handle` -- A node handle. This string will uniquely identify the node
397 /// in the scene.
398 ///
399 /// If the supplied handle matches an existing node, the function does
400 /// nothing if all other parameters match the call which created that
401 /// node. Otherwise, it emits an error. Note that handles need only be
402 /// unique within a given [`Context`]. It is ok to reuse the same
403 /// handle inside different [`Context`]s.
404 ///
405 /// * `node_type` -- The type of node to create. The crate has `&str`
406 /// constants for all [`node`]s that are in the official NSI
407 /// specification. As this parameter is just a string you can instance
408 /// other node types that a particular implementation may provide and
409 /// which are not part of the official specification.
410 ///
411 /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
412 /// *There are no optional arguments defined as of now*.
413 ///
414 /// ```
415 /// # use nsi_ffi_wrap as nsi;
416 /// // Create a context to send the scene to.
417 /// let ctx = nsi::Context::new(None).unwrap();
418 ///
419 /// // Create an infinte plane.
420 /// ctx.create("ground", nsi::PLANE, None);
421 /// ```
422 #[inline]
423 pub fn create(
424 &self,
425 handle: &str,
426 node_type: &str,
427 args: Option<&ArgSlice<'_, 'a>>,
428 ) {
429 let handle = HandleString::from(handle);
430 let node_type = ustr(node_type);
431 let (args_len, args_ptr, _args_out) = to_c_param_vec(args);
432
433 self.0.api.NSICreate(
434 self.0.context,
435 handle.as_char_ptr(),
436 node_type.as_char_ptr(),
437 args_len,
438 args_ptr,
439 );
440 }
441
442 /// This function deletes a node from the scene. All connections to and from
443 /// the node are also deleted.
444 ///
445 /// Note that it is not possible to delete the `.root` or the `.global`
446 /// node.
447 ///
448 /// # Arguments
449 ///
450 /// * `handle` -- A handle to a node previously created with
451 /// [`create()`](Context::create()).
452 ///
453 /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
454 ///
455 /// # Optional Arguments
456 ///
457 /// * `"recursive"` ([`I32`]) -- Specifies whether deletion is
458 /// recursive. By default, only the specified node is deleted. If a value
459 /// of `1` is given, then nodes which connect to the specified node are
460 /// recursively removed. Unless they meet one of the following conditions:
461 /// * They also have connections which do not eventually lead to the
462 /// specified node.
463 /// * Their connection to the node to be deleted was created with a
464 /// `strength` greater than `0`.
465 ///
466 /// This allows, for example, deletion of an entire shader network in a
467 /// single call.
468 #[inline]
469 pub fn delete(&self, handle: &str, args: Option<&ArgSlice<'_, 'a>>) {
470 let handle = HandleString::from(handle);
471 let (args_len, args_ptr, _args_out) = to_c_param_vec(args);
472
473 self.0.api.NSIDelete(
474 self.0.context,
475 handle.as_char_ptr(),
476 args_len,
477 args_ptr,
478 );
479 }
480
481 /// This functions sets attributes on a previously node.
482 /// All optional arguments of the function become attributes of
483 /// the node.
484 ///
485 /// On a [`shader`](`node::SHADER`), this function is used to set the
486 /// implicitly defined shader arguments.
487 ///
488 /// Setting an attribute using this function replaces any value
489 /// previously set by [`set_attribute()`](Context::set_attribute()) or
490 /// [`set_attribute_at_time()`](Context::set_attribute_at_time()).
491 ///
492 /// To reset an attribute to its default value, use
493 /// [`delete_attribute()`](Context::delete_attribute()).
494 ///
495 /// # Arguments
496 ///
497 /// * `handle` -- A handle to a node previously created with
498 /// [`create()`](Context::create()).
499 ///
500 /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
501 #[inline]
502 pub fn set_attribute(&self, handle: &str, args: &ArgSlice<'_, 'a>) {
503 self.0.own_callbacks(handle, Some(args));
504
505 let handle = HandleString::from(handle);
506 let (args_len, args_ptr, _args_out) = to_c_param_vec(Some(args));
507
508 self.0.api.NSISetAttribute(
509 self.0.context,
510 handle.as_char_ptr(),
511 args_len,
512 args_ptr,
513 );
514 }
515
516 /// This function sets time-varying attributes (i.e. motion blurred).
517 ///
518 /// The `time` argument specifies at which time the attribute is being
519 /// defined.
520 ///
521 /// It is not required to set time-varying attributes in any
522 /// particular order. In most uses, attributes that are motion blurred must
523 /// have the same specification throughout the time range.
524 ///
525 /// A notable exception is the `P` attribute on [`particles`
526 /// node](`node::PARTICLES`) which can be of different size for each
527 /// time step because of appearing or disappearing particles. Setting an
528 /// attribute using this function replaces any value previously set by
529 /// [`set_attribute()`](Context::set_attribute()).
530 ///
531 /// # Arguments
532 ///
533 /// * `handle` -- A handle to a node previously created with
534 /// [`create()`](Context::create()).
535 ///
536 /// * `time` -- The time at which to set the value.
537 ///
538 /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
539 #[inline]
540 pub fn set_attribute_at_time(
541 &self,
542 handle: &str,
543 time: f64,
544 args: &ArgSlice<'_, 'a>,
545 ) {
546 self.0.own_callbacks(handle, Some(args));
547
548 let handle = HandleString::from(handle);
549 let (args_len, args_ptr, _args_out) = to_c_param_vec(Some(args));
550
551 self.0.api.NSISetAttributeAtTime(
552 self.0.context,
553 handle.as_char_ptr(),
554 time,
555 args_len,
556 args_ptr,
557 );
558 }
559
560 /// This function deletes any attribute with a name which matches
561 /// the `name` argument on the specified object. There is no way to
562 /// delete an attribute only for a specific time value.
563 ///
564 /// Deleting an attribute resets it to its default value.
565 ///
566 /// For example, after deleting the `transformationmatrix` attribute
567 /// on a [`transform` node](`node::TRANSFORM`), the transform will be an
568 /// identity. Deleting a previously set attribute on a [`shader`
569 /// node](`node::SHADER`) will default to whatever is declared inside
570 /// the shader.
571 ///
572 /// # Arguments
573 ///
574 /// * `handle` -- A handle to a node previously created with
575 /// [`create()`](Context::create()).
576 ///
577 /// * `name` -- The name of the attribute to be deleted/reset.
578 #[inline]
579 pub fn delete_attribute(&self, handle: &str, name: &str) {
580 let handle = HandleString::from(handle);
581 let name = ustr(name);
582
583 self.0.api.NSIDeleteAttribute(
584 self.0.context,
585 handle.as_char_ptr(),
586 name.as_char_ptr(),
587 );
588 }
589
590 /// Create a connection between two elements.
591 ///
592 /// It is not an error to create a connection which already exists
593 /// or to remove a connection which does not exist but the nodes
594 /// on which the connection is performed must exist.
595 ///
596 /// # Arguments
597 ///
598 /// * `from` -- The handle of the node from which the connection is made.
599 ///
600 /// * `from_attribute` -- The name of the attribute from which the connection is
601 /// made.
602 ///
603 /// If this is `None` or `Some("")` the `from` node itself will be
604 /// connected.
605 ///
606 /// * `to` -- The handle of the node to which the connection is made.
607 ///
608 /// * `to_attribute` -- The name of the attribute to which the connection is
609 /// made. If this is an empty string then the connection is made to the
610 /// node instead of to a specific attribute of the node.
611 ///
612 /// # Optional Arguments
613 ///
614 /// * `"value"` -- This can be used to change the value of a node's
615 /// attribute in some contexts. Refer to guidelines on inter-object
616 /// visibility for more information about the utility of this parameter.
617 ///
618 /// * `"priority"` ([`I32`]) -- When connecting attribute nodes,
619 /// indicates in which order the nodes should be considered when
620 /// evaluating the value of an attribute.
621 ///
622 /// * `"strength"` ([`I32`]) -- A connection with a `strength` greater
623 /// than `0` will *block* the progression of a recursive
624 /// [`delete()`](Context::delete()).
625 #[inline]
626 pub fn connect(
627 &self,
628 from: &str,
629 from_attribute: Option<&str>,
630 to: &str,
631 to_attribute: &str,
632 args: Option<&ArgSlice<'_, 'a>>,
633 ) {
634 let from = HandleString::from(from);
635 let from_attribute = ustr(from_attribute.unwrap_or(""));
636 let to = HandleString::from(to);
637 let to_attribute = ustr(to_attribute);
638 let (args_len, args_ptr, _args_out) = to_c_param_vec(args);
639
640 self.0.api.NSIConnect(
641 self.0.context,
642 from.as_char_ptr(),
643 from_attribute.as_char_ptr(),
644 to.as_char_ptr(),
645 to_attribute.as_char_ptr(),
646 args_len,
647 args_ptr,
648 );
649 }
650
651 /// This function removes a connection between two elements.
652 ///
653 /// The handle for either node may be the special value
654 /// [`.all`](crate::node::ALL). This will remove all connections which
655 /// match the other three arguments.
656 ///
657 /// # Examples
658 ///
659 /// ```
660 /// # use nsi_ffi_wrap as nsi;
661 /// # // Create a rendering context.
662 /// # let ctx = nsi::Context::new(None).unwrap();
663 /// // Disconnect everything from the scene's root.
664 /// ctx.disconnect(nsi::ALL, None, ".root", "");
665 /// ```
666 #[inline]
667 pub fn disconnect(
668 &self,
669 from: &str,
670 from_attribute: Option<&str>,
671 to: &str,
672 to_attribute: &str,
673 ) {
674 let from = HandleString::from(from);
675 let from_attribute = ustr(from_attribute.unwrap_or(""));
676 let to = HandleString::from(to);
677 let to_attribute = ustr(to_attribute);
678
679 self.0.api.NSIDisconnect(
680 self.0.context,
681 from.as_char_ptr(),
682 from_attribute.as_char_ptr(),
683 to.as_char_ptr(),
684 to_attribute.as_char_ptr(),
685 );
686 }
687
688 /// This function includes a block of interface calls from an external
689 /// source into the current scene. It blends together the concepts of a
690 /// file include, commonly known as an *archive*, with that of
691 /// procedural include which is traditionally a compiled executable. Both
692 /// are the same idea expressed in a different language.
693 ///
694 /// Note that for delayed procedural evaluation you should use a
695 /// `Procedural` node.
696 ///
697 /// The ɴꜱɪ adds a third option which sits in-between -- [Lua
698 /// scripts](https://nsi.readthedocs.io/en/latest/lua-api.html). They are more powerful than a
699 /// simple included file yet they are also easier to generate as they do not
700 /// require compilation.
701 ///
702 /// For example, it is realistic to export a whole new script for every
703 /// frame of an animation. It could also be done for every character in
704 /// a frame. This gives great flexibility in how components of a scene
705 /// are put together.
706 ///
707 /// The ability to load ɴꜱɪ commands from memory is also provided.
708 ///
709 /// # Optional Arguments
710 ///
711 /// * `"type"` ([`String`]) -- The type of file which will generate the
712 /// interface calls. This can be one of:
713 /// * `"apistream"` -- Read in an ɴꜱɪ stream. This requires either
714 /// `"filename"` or `"buffer"`/`"size"` arguments to be specified too.
715 ///
716 /// * `"lua"` -- Execute a Lua script, either from file or inline. See also
717 /// [how to evaluate a Lua script](https://nsi.readthedocs.io/en/latest/lua-api.html#luaapi-evaluation).
718 ///
719 /// * `"dynamiclibrary"` -- Execute native compiled code in a loadable library. See
720 /// [dynamic library procedurals](https://nsi.readthedocs.io/en/latest/procedurals.html#section-procedurals)
721 /// for an implementation example in C.
722 ///
723 /// * `"filename"` ([`String`]) -- The name of the file which contains the
724 /// interface calls to include.
725 ///
726 /// * `"script"` ([`String`]) -- A valid Lua script to execute when `"type"`
727 /// is set to `"lua"`.
728 ///
729 /// * `"buffer"` ([`String`]) -- A memory block that contain ɴꜱɪ commands to
730 /// execute.
731 ///
732 /// * `"backgroundload"` ([`I32`]) -- If this is nonzero, the object may
733 /// be loaded in a separate thread, at some later time. This requires that
734 /// further interface calls not directly reference objects defined in the
735 /// included file. The only guarantee is that the file will be loaded
736 /// before rendering begins.
737 #[inline]
738 pub fn evaluate(&self, args: &ArgSlice<'_, 'a>) {
739 let (args_len, args_ptr, _args_out) = to_c_param_vec(Some(args));
740
741 self.0.api.NSIEvaluate(self.0.context, args_len, args_ptr);
742 }
743
744 /// This function is the only control function of the API.
745 ///
746 /// It is responsible of starting, suspending and stopping the render. It
747 /// also allows for synchronizing the render with interactive calls that
748 /// might have been issued.
749 ///
750 /// Note that this call will block if [`Action::Wait`] is selected.
751 ///
752 /// # Arguments
753 ///
754 /// * `action` -- Specifies the render [`Action`] to be performed on the
755 /// scene.
756 ///
757 /// # Optional Arguments
758 ///
759 /// * `"progressive"` ([`I32`]) -- If set to `1`, render the image in a
760 /// progressive fashion.
761 ///
762 /// * `"interactive"` ([`I32`]) -- If set to `1`, the renderer will
763 /// accept commands to edit scene’s state while rendering. The difference
764 /// with a normal render is that the render task will not exit even if
765 /// rendering is finished. Interactive renders are by definition
766 /// progressive.
767 ///
768 /// * `"callback"` ([`FnStatus`]) -- A closure that will be called be when
769 /// the status of the render changes.
770 ///
771 /// # Example
772 ///
773 /// ```
774 /// # use nsi_ffi_wrap as nsi;
775 /// # let ctx = nsi::Context::new(None).unwrap();
776 /// let status_callback = nsi::StatusCallback::new(
777 /// |_ctx: &nsi::Context, status: nsi::RenderStatus| {
778 /// println!("Status: {:?}", status);
779 /// },
780 /// );
781 ///
782 /// /// The renderer will abort because we didn't define an output driver.
783 /// /// So our status_callback() above will receive RenderStatus::Aborted.
784 /// ctx.render_control(
785 /// nsi::Action::Start,
786 /// Some(&[
787 /// nsi::i32!("interactive", true as _),
788 /// nsi::callback!("callback", status_callback),
789 /// ]),
790 /// );
791 ///
792 /// // Block until the renderer is really done.
793 /// ctx.render_control(nsi::Action::Wait, None);
794 /// ```
795 #[inline]
796 pub fn render_control(
797 &self,
798 action: Action,
799 args: Option<&ArgSlice<'_, 'a>>,
800 ) {
801 let (_, _, mut args_out) = to_c_param_vec(args);
802
803 let fn_pointer: nsi_sys::NSIRenderStopped =
804 Some(render_status as extern "C" fn(*mut c_void, c_int, c_int));
805
806 args_out.push(nsi_sys::NSIParam {
807 name: ustr("action").as_char_ptr(),
808 data: &ustr(action.as_str()).as_char_ptr() as *const _ as _,
809 type_: NSIType::String as _,
810 arraylength: 0,
811 count: 1,
812 flags: 0,
813 });
814
815 if let Some(args) = args
816 && let Some(arg) =
817 args.iter().find(|arg| ustr("callback") == arg.name)
818 {
819 // See the `errorhandlerdata` note in `Context::new`.
820 args_out.push(nsi_sys::NSIParam {
821 name: ustr("stoppedcallback").as_char_ptr(),
822 data: &fn_pointer as *const _ as _,
823 type_: NSIType::Pointer as _,
824 arraylength: 0,
825 count: 1,
826 flags: 0,
827 });
828 args_out.push(nsi_sys::NSIParam {
829 name: ustr("stoppedcallbackdata").as_char_ptr(),
830 data: arg.data.as_c_ptr(),
831 type_: NSIType::Pointer as _,
832 arraylength: 1,
833 count: 1,
834 flags: 0,
835 });
836 }
837
838 self.0.own_callbacks(".context", args);
839
840 self.0.api.NSIRenderControl(
841 self.0.context,
842 args_out.len() as _,
843 args_out.as_ptr(),
844 );
845
846 // `Stop` and `Wait` return only once the render is done, so any
847 // callback displaced by an earlier `set_attribute` is now
848 // unreachable and can be freed.
849 if matches!(action, Action::Stop | Action::Wait) {
850 self.0.reclaim_retired();
851 }
852 }
853}
854
855// Action is re-exported from nsi-trait via lib.rs
856
857/// The status of a *interactive* render session.
858#[repr(i32)]
859#[derive(Debug, Copy, Clone, PartialEq, Eq, num_enum::FromPrimitive)]
860pub enum RenderStatus {
861 #[num_enum(default)]
862 Completed = nsi_sys::NSIStoppingStatus::RenderCompleted as _,
863 Aborted = nsi_sys::NSIStoppingStatus::RenderAborted as _,
864 Synchronized = nsi_sys::NSIStoppingStatus::RenderSynchronized as _,
865 Restarted = nsi_sys::NSIStoppingStatus::RenderRestarted as _,
866}
867
868/// A closure which is called to inform about the status of an ongoing render.
869///
870/// It is passed to ɴsɪ via [`render_control()`](Context::render_control())’s
871/// `"callback"` argument.
872///
873/// # Examples
874///
875/// ```
876/// # use nsi_ffi_wrap as nsi;
877/// # let ctx = nsi::Context::new(None).unwrap();
878/// let status_callback = nsi::context::StatusCallback::new(
879/// |_: &nsi::context::Context, status: nsi::context::RenderStatus| {
880/// println!("Status: {:?}", status);
881/// },
882/// );
883///
884/// ctx.render_control(
885/// nsi::Action::Start,
886/// Some(&[nsi::callback!("callback", status_callback)]),
887/// );
888/// ```
889pub trait FnStatus<'a>: Fn(
890 // The [`Context`] for which this closure was called.
891 &Context,
892 // Status of interactive render session.
893 RenderStatus,
894)
895+ 'a {}
896
897#[doc(hidden)]
898impl<
899 'a,
900 T: Fn(&Context, RenderStatus)
901 + 'a
902 + for<'r, 's> Fn(&'r context::Context<'s>, RenderStatus),
903> FnStatus<'a> for T
904{
905}
906
907// FIXME once trait aliases are in stable.
908/*
909trait FnStatus<'a> = FnMut(
910 // Status of interactive render session.
911 status: RenderStatus
912 )
913 + 'a
914*/
915
916/// Wrapper to pass a [`FnStatus`] closure to a [`Context`].
917pub struct StatusCallback<'a>(Box<Box<dyn FnStatus<'a>>>);
918
919unsafe impl Send for StatusCallback<'static> {}
920unsafe impl Sync for StatusCallback<'static> {}
921
922impl<'a> StatusCallback<'a> {
923 pub fn new<F>(fn_status: F) -> Self
924 where
925 F: FnStatus<'a>,
926 {
927 StatusCallback(Box::new(Box::new(fn_status)))
928 }
929}
930
931impl CallbackPtr for StatusCallback<'_> {
932 #[doc(hidden)]
933 fn to_ptr(self) -> *const core::ffi::c_void {
934 Box::into_raw(self.0) as *const _ as _
935 }
936
937 #[doc(hidden)]
938 unsafe fn drop_ptr(ptr: *const core::ffi::c_void) {
939 // SAFETY: `ptr` came from `to_ptr` above and is reclaimed once.
940 drop(unsafe { Box::from_raw(ptr as *mut Box<dyn FnStatus<'static>>) });
941 }
942}
943
944// Trampoline function for the FnStatus callback.
945#[unsafe(no_mangle)]
946pub(crate) extern "C" fn render_status(
947 payload: *mut c_void,
948 context: nsi_sys::NSIContext,
949 status: c_int,
950) {
951 // Catch any panics to prevent unwinding into C code.
952 let _ = std::panic::catch_unwind(|| {
953 if !payload.is_null() {
954 // SAFETY: The payload originates from `StatusCallback::to_ptr`, which
955 // leaks a `Box<dyn FnStatus>`. We only borrow it here and never take
956 // ownership, so the pointer stays valid for the lifetime of the
957 // context.
958 let fn_status = unsafe { &*(payload as *mut Box<dyn FnStatus>) };
959 // The renderer that made this context, so a closure
960 // calling back into it reaches the right one. See
961 // `crate::renderer_of`.
962 let Ok(api) = crate::renderer_of(context) else {
963 return;
964 };
965 let ctx = Context(Arc::new(InnerContext::new(context, api)));
966
967 fn_status(&ctx, status.into());
968
969 // We must not call drop() on this context.
970 // This is safe as Context doesn't allocate and this one is on
971 // the stack anyway.
972 std::mem::forget(ctx);
973 }
974 });
975 // If we panic, we can't do much -- just return silently.
976}
977
978/// A closure which is called to inform about the errors during scene defintion
979/// or a render.
980///
981/// It is passed to ɴsɪ via [`new()`](Context::new())’s `"errorhandler"`
982/// argument.
983///
984/// # Examples
985///
986/// ```
987/// # use nsi_ffi_wrap as nsi;
988/// use log::{Level, debug, error, info, trace, warn};
989///
990/// let error_handler = nsi::ErrorCallback::new(
991/// |level: Level, message_id: i32, message: &str| match level {
992/// Level::Error => error!("[{}] {}", message_id, message),
993/// Level::Warn => warn!("[{}] {}", message_id, message),
994/// Level::Info => info!("[{}] {}", message_id, message),
995/// Level::Debug => debug!("[{}] {}", message_id, message),
996/// Level::Trace => trace!("[{}] {}", message_id, message),
997/// },
998/// );
999///
1000/// let ctx = nsi::Context::new(Some(&[nsi::callback!(
1001/// "errorhander",
1002/// error_handler
1003/// )]))
1004/// .unwrap();
1005///
1006/// // Do something with ctx ...
1007/// ```
1008pub trait FnError<'a>: Fn(
1009 // The error level.
1010 log::Level,
1011 // The message id.
1012 i32,
1013 // The message.
1014 &str,
1015)
1016+ 'a {}
1017
1018#[doc(hidden)]
1019impl<
1020 'a,
1021 T: Fn(log::Level, i32, &str) + 'a + for<'r> Fn(log::Level, i32, &'r str),
1022> FnError<'a> for T
1023{
1024}
1025
1026/// Wrapper to pass a [`FnError`] closure to a [`Context`].
1027pub struct ErrorCallback<'a>(Box<Box<dyn FnError<'a>>>);
1028
1029unsafe impl Send for ErrorCallback<'static> {}
1030unsafe impl Sync for ErrorCallback<'static> {}
1031
1032impl<'a> ErrorCallback<'a> {
1033 pub fn new<F>(fn_error: F) -> Self
1034 where
1035 F: FnError<'a>,
1036 {
1037 ErrorCallback(Box::new(Box::new(fn_error)))
1038 }
1039}
1040
1041impl CallbackPtr for ErrorCallback<'_> {
1042 #[doc(hidden)]
1043 fn to_ptr(self) -> *const core::ffi::c_void {
1044 Box::into_raw(self.0) as *const _ as _
1045 }
1046
1047 #[doc(hidden)]
1048 unsafe fn drop_ptr(ptr: *const core::ffi::c_void) {
1049 // SAFETY: `ptr` came from `to_ptr` above and is reclaimed once.
1050 drop(unsafe { Box::from_raw(ptr as *mut Box<dyn FnError<'static>>) });
1051 }
1052}
1053
1054// Trampoline function for the FnError callback.
1055#[unsafe(no_mangle)]
1056pub(crate) extern "C" fn error_handler(
1057 payload: *mut c_void,
1058 level: c_int,
1059 code: c_int,
1060 message: *const c_char,
1061) {
1062 // Catch any panics to prevent unwinding into C code.
1063 let _ = std::panic::catch_unwind(|| {
1064 if !payload.is_null() {
1065 // SAFETY: The payload originates from `ErrorCallback::to_ptr`, which
1066 // leaks a `Box<dyn FnError>`. We only borrow it here and never take
1067 // ownership, so the pointer stays valid for the lifetime of the
1068 // context.
1069 let fn_error = unsafe { &*(payload as *mut Box<dyn FnError>) };
1070
1071 // SAFETY: message pointer comes from NSI C API and should be valid.
1072 // We validate it is not null and handle UTF-8 errors gracefully.
1073 let message = if message.is_null() {
1074 "<null message>"
1075 } else {
1076 let c_str = unsafe { CStr::from_ptr(message as _) };
1077 match c_str.to_str() {
1078 Ok(s) => s,
1079 Err(_) => {
1080 // If the string is not valid UTF-8, use lossy conversion
1081 // This prevents crashes while still conveying the message
1082 &c_str.to_string_lossy()
1083 }
1084 }
1085 };
1086
1087 let level = match NSIErrorLevel::from(level) {
1088 NSIErrorLevel::Message => log::Level::Trace,
1089 NSIErrorLevel::Info => log::Level::Info,
1090 NSIErrorLevel::Warning => log::Level::Warn,
1091 NSIErrorLevel::Error => log::Level::Error,
1092 };
1093
1094 fn_error(level, code as _, message);
1095 }
1096 });
1097 // If we panic, we cannot do much - just return silently.
1098}