nsi_core/
context.rs

1//! An ɴsɪ context.
2
3// Needed for the example dode to build.
4extern crate self as nsi;
5use crate::{argument::*, *};
6use null_terminated_str::NullTerminatedStr;
7use rclite::Arc;
8#[allow(unused_imports)]
9use std::{
10    ffi::{c_char, CStr, CString},
11    marker::PhantomData,
12    ops::Drop,
13    os::raw::{c_int, c_void},
14};
15use ustr::Ustr;
16
17/// The actual context and a marker to hold on to callbacks
18/// (closures)/references passed via [`set_attribute()`] or the like.
19///
20/// We wrap this in an [`Arc`] in [`Context`] to make sure drop() is only
21/// called when the last clone ceases existing.
22#[derive(Clone, Debug, Hash, PartialEq, Eq)]
23struct InnerContext<'a> {
24    context: NSIContext,
25    // _marker needs to be invariant in 'a.
26    // See "Making a struct outlive a parameter given to a method of
27    // that struct": https://stackoverflow.com/questions/62374326/
28    _marker: PhantomData<*mut &'a ()>,
29}
30
31unsafe impl<'a> Send for InnerContext<'a> {}
32unsafe impl<'a> Sync for InnerContext<'a> {}
33
34impl<'a> Drop for InnerContext<'a> {
35    #[inline]
36    fn drop(&mut self) {
37        NSI_API.NSIEnd(self.context);
38    }
39}
40
41/// # An ɴꜱɪ Context.
42///
43/// A `Context` is used to describe a scene to the renderer and request images
44/// to be rendered from it.
45///
46/// ## Safety
47/// A `Context` may be used in multiple threads at once.
48///
49/// ## Lifetime
50/// A `Context` can be used without worrying about its lifetime until you want
51/// to store it somewhere, e.g. in a struct.
52///
53/// The reason `Context` has an explicit lifetime is so that it can take
54/// [`Reference`]s and [`Callback`]s (closures). These must be valid until the
55/// context is dropped and this guarantee requires explicit lifetimes. When you
56/// use a context directly this is not an issue but when you want to reference
57/// it somewhere the same rules as with all references apply.
58///
59/// ## Further Reading
60/// See the [ɴꜱɪ documentation on context
61/// handling](https://nsi.readthedocs.io/en/latest/c-api.html#context-handling).
62#[derive(Clone, Debug, Hash, PartialEq, Eq)]
63pub struct Context<'a>(Arc<InnerContext<'a>>);
64
65unsafe impl<'a> Send for Context<'a> {}
66unsafe impl<'a> Sync for Context<'a> {}
67
68impl<'a> From<Context<'a>> for NSIContext {
69    #[inline]
70    fn from(context: Context<'a>) -> Self {
71        context.0.context
72    }
73}
74
75impl<'a> From<NSIContext> for Context<'a> {
76    #[inline]
77    fn from(context: NSIContext) -> Self {
78        Self(Arc::new(InnerContext {
79            context,
80            _marker: PhantomData,
81        }))
82    }
83}
84
85impl<'a> Context<'a> {
86    /// Creates an ɴsɪ context.
87    ///
88    /// Contexts may be used in multiple threads at once.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// # use nsi_core as nsi;
94    /// // Create rendering context that dumps to stdout.
95    /// let ctx =
96    ///     nsi::Context::new(Some(&[nsi::string!("streamfilename", "stdout")]))
97    ///         .expect("Could not create ɴsɪ context.");
98    /// ```
99    /// # Error
100    /// If this method fails for some reason, it returns [`None`].
101    #[inline]
102    pub fn new(args: Option<&ArgSlice<'_, 'a>>) -> Option<Self> {
103        let (_, _, mut args_out) = get_c_param_vec(args);
104
105        let fn_pointer: nsi_sys::NSIErrorHandler = Some(
106            error_handler
107                as extern "C" fn(*mut c_void, c_int, c_int, *const c_char),
108        );
109
110        if let Some(args) = args {
111            if let Some(arg) = args
112                .iter()
113                .find(|arg| Ustr::from("errorhandler") == arg.name)
114            {
115                args_out.push(nsi_sys::NSIParam {
116                    name: Ustr::from("errorhandler").as_char_ptr(),
117                    data: &fn_pointer as *const _ as _,
118                    type_: NSIType::Pointer as _,
119                    arraylength: 0,
120                    count: 1,
121                    flags: 0,
122                });
123                args_out.push(nsi_sys::NSIParam {
124                    name: Ustr::from("errorhandlerdata").as_char_ptr(),
125                    data: &arg.data.as_c_ptr() as *const _ as _,
126                    type_: NSIType::Pointer as _,
127                    arraylength: 1,
128                    count: 1,
129                    flags: 0,
130                });
131            }
132        }
133
134        let context = NSI_API.NSIBegin(args_out.len() as _, args_out.as_ptr());
135
136        if 0 == context {
137            None
138        } else {
139            Some(Self(Arc::new(InnerContext {
140                context,
141                _marker: PhantomData,
142            })))
143        }
144    }
145
146    /// Creates a new node.
147    ///
148    /// # Arguments
149    ///
150    /// * `handle` -- A node handle. This string will uniquely identify the node
151    ///   in the scene.
152    ///
153    ///   If the supplied handle matches an existing node, the function does
154    /// nothing if all other parameters match the call which created that
155    /// node. Otherwise, it emits an error. Note that handles need only be
156    /// unique within a given [`Context`]. It is ok to reuse the same
157    /// handle inside different [`Context`]s.
158    ///
159    /// * `node_type` -- The type of node to create. The crate has `&str`
160    ///   constants for all [`node`]s that are in the official NSI
161    ///   specification. As this parameter is just a string you can instance
162    ///   other node types that a particular implementation may provide and
163    ///   which are not part of the official specification.
164    ///
165    /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
166    ///   *There are no optional arguments defined as of now*.
167    ///
168    /// ```
169    /// # use nsi_core as nsi;
170    /// // Create a context to send the scene to.
171    /// let ctx = nsi::Context::new(None).unwrap();
172    ///
173    /// // Create an infinte plane.
174    /// ctx.create("ground", nsi::PLANE, None);
175    /// ```
176    #[inline]
177    pub fn create(
178        &self,
179        handle: &str,
180        node_type: &str,
181        args: Option<&ArgSlice<'_, 'a>>,
182    ) {
183        let handle = HandleString::from(handle);
184        let node_type = Ustr::from(node_type);
185        let (args_len, args_ptr, _args_out) = get_c_param_vec(args);
186
187        NSI_API.NSICreate(
188            self.0.context,
189            handle.as_char_ptr(),
190            node_type.as_char_ptr(),
191            args_len,
192            args_ptr,
193        );
194    }
195
196    /// This function deletes a node from the scene. All connections to and from
197    /// the node are also deleted.
198    ///
199    /// Note that it is not possible to delete the `.root` or the `.global`
200    /// node.
201    ///
202    /// # Arguments
203    ///
204    /// * `handle` -- A handle to a node previously created with
205    ///   [`create()`](Context::create()).
206    ///
207    /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
208    ///
209    /// # Optional Arguments
210    ///
211    /// * `"recursive"` ([`Integer`]) -- Specifies whether deletion is
212    ///   recursive. By default, only the specified node is deleted. If a value
213    ///   of `1` is given, then nodes which connect to the specified node are
214    ///   recursively removed. Unless they meet one of the following conditions:
215    ///   * They also have connections which do not eventually lead to the
216    ///     specified node.
217    ///   * Their connection to the node to be deleted was created with a
218    ///     `strength` greater than `0`.
219    ///
220    ///   This allows, for example, deletion of an entire shader network in a
221    ///   single call.
222    #[inline]
223    pub fn delete(&self, handle: &str, args: Option<&ArgSlice<'_, 'a>>) {
224        let handle = HandleString::from(handle);
225        let (args_len, args_ptr, _args_out) = get_c_param_vec(args);
226
227        NSI_API.NSIDelete(
228            self.0.context,
229            handle.as_char_ptr(),
230            args_len,
231            args_ptr,
232        );
233    }
234
235    /// This functions sets attributes on a previously node.
236    /// All optional arguments of the function become attributes of
237    /// the node.
238    ///
239    /// On a [`shader`](`node::SHADER`), this function is used to set the
240    /// implicitly defined shader arguments.
241    ///
242    /// Setting an attribute using this function replaces any value
243    /// previously set by [`set_attribute()`](Context::set_attribute()) or
244    /// [`set_attribute_at_time()`](Context::set_attribute_at_time()).
245    ///
246    /// To reset an attribute to its default value, use
247    /// [`delete_attribute()`](Context::delete_attribute()).
248    ///
249    /// # Arguments
250    ///
251    /// * `handle` -- A handle to a node previously created with
252    ///   [`create()`](Context::create()).
253    ///
254    /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
255    #[inline]
256    pub fn set_attribute(&self, handle: &str, args: &ArgSlice<'_, 'a>) {
257        let handle = HandleString::from(handle);
258        let (args_len, args_ptr, _args_out) = get_c_param_vec(Some(args));
259
260        NSI_API.NSISetAttribute(
261            self.0.context,
262            handle.as_char_ptr(),
263            args_len,
264            args_ptr,
265        );
266    }
267
268    /// This function sets time-varying attributes (i.e. motion blurred).
269    ///
270    /// The `time` argument specifies at which time the attribute is being
271    /// defined.
272    ///
273    /// It is not required to set time-varying attributes in any
274    /// particular order. In most uses, attributes that are motion blurred must
275    /// have the same specification throughout the time range.
276    ///
277    /// A notable  exception is the `P` attribute on [`particles`
278    /// node](`node::PARTICLES`) which can be of different size for each
279    /// time step because of appearing or disappearing particles. Setting an
280    /// attribute using this function replaces any value previously set by
281    /// [`set_attribute()`](Context::set_attribute()).
282    ///
283    /// # Arguments
284    ///
285    /// * `handle` -- A handle to a node previously created with
286    ///   [`create()`](Context::create()).
287    ///
288    /// * `time` -- The time at which to set the value.
289    ///
290    /// * `args` -- A [`slice`](std::slice) of optional [`Arg`] arguments.
291    #[inline]
292    pub fn set_attribute_at_time(
293        &self,
294        handle: &str,
295        time: f64,
296        args: &ArgSlice<'_, 'a>,
297    ) {
298        let handle = HandleString::from(handle);
299        let (args_len, args_ptr, _args_out) = get_c_param_vec(Some(args));
300
301        NSI_API.NSISetAttributeAtTime(
302            self.0.context,
303            handle.as_char_ptr(),
304            time,
305            args_len,
306            args_ptr,
307        );
308    }
309
310    /// This function deletes any attribute with a name which matches
311    /// the `name` argument on the specified object. There is no way to
312    /// delete an attribute only for a specific time value.
313    ///
314    /// Deleting an attribute resets it to its default value.
315    ///
316    /// For example, after deleting the `transformationmatrix` attribute
317    /// on a [`transform` node](`node::TRANSFORM`), the transform will be an
318    /// identity. Deleting a previously set attribute on a [`shader`
319    /// node](`node::SHADER`) will default to whatever is declared inside
320    /// the shader.
321    ///
322    /// # Arguments
323    ///
324    /// * `handle` -- A handle to a node previously created with
325    ///   [`create()`](Context::create()).
326    ///
327    /// * `name` -- The name of the attribute to be deleted/reset.
328    #[inline]
329    pub fn delete_attribute(&self, handle: &str, name: &str) {
330        let handle = HandleString::from(handle);
331        let name = Ustr::from(name);
332
333        NSI_API.NSIDeleteAttribute(
334            self.0.context,
335            handle.as_char_ptr(),
336            name.as_char_ptr(),
337        );
338    }
339
340    /// Create a connection between two elements.
341    ///
342    /// It is not an error to create a connection which already exists
343    /// or to remove a connection which does not exist but the nodes
344    /// on which the connection is performed must exist.
345    ///
346    /// # Arguments
347    ///
348    /// * `from` -- The handle of the node from which the connection is made.
349    ///
350    /// * `from_attr` -- The name of the attribute from which the connection is
351    ///   made. If this is an empty string then the connection is made from the
352    ///   node instead of from a specific attribute of the node.
353    ///
354    ///   If this is `None` the node itself will be connected.
355    ///
356    /// * `to` -- The handle of the node to which the connection is made.
357    ///
358    /// * `to_attr` -- The name of the attribute to which the connection is
359    ///   made. If this is an empty string then the connection is made to the
360    ///   node instead of to a specific attribute of the node.
361    ///
362    /// # Optional Arguments
363    ///
364    /// * `"value"` -- This can be used to change the value of a node's
365    ///   attribute in some contexts. Refer to guidelines on inter-object
366    ///   visibility for more information about the utility of this parameter.
367    ///
368    /// * `"priority"` ([`Integer`]) -- When connecting attribute nodes,
369    ///   indicates in which order the nodes should be considered when
370    ///   evaluating the value of an attribute.
371    ///
372    /// * `"strength"` ([`Integer`]) -- A connection with a `strength` greater
373    ///   than `0` will *block* the progression of a recursive
374    ///   [`delete()`](Context::delete()).
375    #[inline]
376    pub fn connect(
377        &self,
378        from: &str,
379        from_attr: Option<&str>,
380        to: &str,
381        to_attr: &str,
382        args: Option<&ArgSlice<'_, 'a>>,
383    ) {
384        let from = HandleString::from(from);
385        let from_attr = Ustr::from(from_attr.unwrap_or(""));
386        let to = HandleString::from(to);
387        let to_attr = Ustr::from(to_attr);
388        let (args_len, args_ptr, _args_out) = get_c_param_vec(args);
389
390        NSI_API.NSIConnect(
391            self.0.context,
392            from.as_char_ptr(),
393            from_attr.as_char_ptr(),
394            to.as_char_ptr(),
395            to_attr.as_char_ptr(),
396            args_len,
397            args_ptr,
398        );
399    }
400
401    /// This function removes a connection between two elements.
402    ///
403    /// The handle for either node may be the special value
404    /// [`.all`](crate::node::ALL). This will remove all connections which
405    /// match the other three arguments.
406    ///
407    /// # Examples
408    ///
409    /// ```
410    /// # use nsi_core as nsi;
411    /// # // Create a rendering context.
412    /// # let ctx = nsi::Context::new(None).unwrap();
413    /// // Disconnect everything from the scene's root.
414    /// ctx.disconnect(nsi::ALL, None, ".root", "");
415    /// ```
416    #[inline]
417    pub fn disconnect(
418        &self,
419        from: &str,
420        from_attr: Option<&str>,
421        to: &str,
422        to_attr: &str,
423    ) {
424        let from = HandleString::from(from);
425        let from_attr = Ustr::from(from_attr.unwrap_or(""));
426        let to = HandleString::from(to);
427        let to_attr = Ustr::from(to_attr);
428
429        NSI_API.NSIDisconnect(
430            self.0.context,
431            from.as_char_ptr(),
432            from_attr.as_char_ptr(),
433            to.as_char_ptr(),
434            to_attr.as_char_ptr(),
435        );
436    }
437
438    /// This function includes a block of interface calls from an external
439    /// source into the current scene. It blends together the concepts of a
440    /// file include, commonly known as an *archive*, with that of
441    /// procedural include which is traditionally a compiled executable. Both
442    /// are the same idea expressed in a different language.
443    ///
444    /// Note that for delayed procedural evaluation you should use a
445    /// `Procedural` node.
446    ///
447    /// The ɴꜱɪ adds a third option which sits in-between — [Lua
448    /// scripts](https://nsi.readthedocs.io/en/latest/lua-api.html). They are more powerful than a
449    /// simple included file yet they are also easier to generate as they do not
450    /// require compilation.
451    ///
452    /// For example, it is realistic to export a whole new script for every
453    /// frame of an animation. It could also be done for every character in
454    /// a frame. This gives great flexibility in how components of a scene
455    /// are put together.
456    ///
457    /// The ability to load ɴꜱɪ commands from memory is also provided.
458    ///
459    /// # Optional Arguments
460    ///
461    /// * `"type"` ([`String`]) – The type of file which will generate the
462    ///   interface calls. This can be one of:
463    ///   * `"apistream"` – Read in an ɴꜱɪ stream. This requires either
464    ///     `"filename"` or `"buffer"`/`"size"` arguments to be specified too.
465    ///
466    ///   * `"lua"` – Execute a Lua script, either from file or inline. See also
467    ///     [how to evaluate a Lua script](https://nsi.readthedocs.io/en/latest/lua-api.html#luaapi-evaluation).
468    ///
469    ///   * `"dynamiclibrary"` – Execute native compiled code in a loadable library. See
470    ///     [dynamic library procedurals](https://nsi.readthedocs.io/en/latest/procedurals.html#section-procedurals)
471    ///     for an implementation example in C.
472    ///
473    /// * `"filename"` ([`String`]) – The name of the file which contains the
474    ///   interface calls to include.
475    ///
476    /// * `"script"` ([`String`]) – A valid Lua script to execute when `"type"`
477    ///   is set to `"lua"`.
478    ///
479    /// * `"buffer"` ([`String`]) – A memory block that contain ɴꜱɪ commands to
480    ///   execute.
481    ///
482    /// * `"backgroundload"` ([`Integer`]) – If this is nonzero, the object may
483    ///   be loaded in a separate thread, at some later time. This requires that
484    ///   further interface calls not directly reference objects defined in the
485    ///   included file. The only guarantee is that the file will be loaded
486    ///   before rendering begins.
487    #[inline]
488    pub fn evaluate(&self, args: &ArgSlice<'_, 'a>) {
489        let (args_len, args_ptr, _args_out) = get_c_param_vec(Some(args));
490
491        NSI_API.NSIEvaluate(self.0.context, args_len, args_ptr);
492    }
493
494    /// This function is the only control function of the API.
495    ///
496    /// It is responsible of starting, suspending and stopping the render. It
497    /// also allows for synchronizing the render with interactive calls that
498    /// might have been issued.
499    ///
500    /// Note that this call will block if [`Action::Wait`] is selected.
501    ///
502    /// # Arguments
503    ///
504    /// * `action` -- Specifies the render [`Action`] to be performed on the
505    ///   scene.
506    ///
507    /// # Optional Arguments
508    ///
509    /// * `"progressive"` ([`Integer`]) -- If set to `1`, render the image in a
510    ///   progressive fashion.
511    ///
512    /// * `"interactive"` ([`Integer`]) -- If set to `1`, the renderer will
513    ///   accept commands to edit scene’s state while rendering. The difference
514    ///   with a normal render is that the render task will not exit even if
515    ///   rendering is finished. Interactive renders are by definition
516    ///   progressive.
517    ///
518    /// * `"callback"` ([`FnStatus`]) -- A closure that will be called be when
519    ///   the status of the render changes.
520    ///
521    ///   # Example
522    ///
523    ///   ```
524    ///   # use nsi_core as nsi;
525    ///   # let ctx = nsi::Context::new(None).unwrap();
526    ///   let status_callback = nsi::StatusCallback::new(
527    ///       |_ctx: &nsi::Context, status: nsi::RenderStatus| {
528    ///           println!("Status: {:?}", status);
529    ///         },
530    ///      );
531    ///
532    ///   /// The renderer will abort because we didn't define an output driver.
533    ///   /// So our status_callback() above will receive RenderStatus::Aborted.
534    ///   ctx.render_control(
535    ///       nsi::Action::Start,
536    ///       Some(&[
537    ///           nsi::integer!("interactive", true as _),
538    ///           nsi::callback!("callback", status_callback),
539    ///       ]),
540    ///   );
541    ///
542    ///   // Block until the renderer is really done.
543    ///   ctx.render_control(nsi::Action::Wait, None);
544    ///   ```
545    #[inline]
546    pub fn render_control(
547        &self,
548        action: Action,
549        args: Option<&ArgSlice<'_, 'a>>,
550    ) {
551        let (_, _, mut args_out) = get_c_param_vec(args);
552
553        let fn_pointer: nsi_sys::NSIRenderStopped =
554            Some(render_status as extern "C" fn(*mut c_void, c_int, c_int));
555
556        args_out.push(nsi_sys::NSIParam {
557            name: Ustr::from("action").as_char_ptr(),
558            data: &Ustr::from(match action {
559                Action::Start => "start",
560                Action::Wait => "wait",
561                Action::Synchronize => "synchronize",
562                Action::Suspend => "suspend",
563                Action::Resume => "resume",
564                Action::Stop => "stop",
565            })
566            .as_char_ptr() as *const _ as _,
567            type_: NSIType::String as _,
568            arraylength: 0,
569            count: 1,
570            flags: 0,
571        });
572
573        if let Some(args) = args {
574            if let Some(arg) =
575                args.iter().find(|arg| Ustr::from("callback") == arg.name)
576            {
577                args_out.push(nsi_sys::NSIParam {
578                    name: Ustr::from("stoppedcallback").as_char_ptr(),
579                    data: &fn_pointer as *const _ as _,
580                    type_: NSIType::Pointer as _,
581                    arraylength: 0,
582                    count: 1,
583                    flags: 0,
584                });
585                args_out.push(nsi_sys::NSIParam {
586                    name: Ustr::from("stoppedcallbackdata").as_char_ptr(),
587                    data: &arg.data.as_c_ptr() as *const _ as _,
588                    type_: NSIType::Pointer as _,
589                    arraylength: 1,
590                    count: 1,
591                    flags: 0,
592                });
593            }
594        }
595
596        NSI_API.NSIRenderControl(
597            self.0.context,
598            args_out.len() as _,
599            args_out.as_ptr(),
600        );
601    }
602}
603
604/// The render action to perform when calling
605/// [`render_control()`](Context::render_control()).
606#[derive(Debug, Copy, Clone, PartialEq, Eq)]
607pub enum Action {
608    /// Starts rendering the scene in the provided context. The render starts
609    /// in parallel -- this does not block.
610    Start,
611    /// Wait for a render to finish. This blocks.
612    Wait,
613    /// For an interactive render, apply all the buffered calls to scene's
614    /// state.
615    Synchronize,
616    /// Suspends render. Does not block.
617    Suspend,
618    /// Resumes a previously suspended render. Does not block.
619    Resume,
620    /// Stops rendering in the provided context without destroying the scene.
621    /// Does not block.
622    Stop,
623}
624
625/// The status of a *interactive* render session.
626#[repr(i32)]
627#[derive(Debug, Copy, Clone, PartialEq, Eq, num_enum::FromPrimitive)]
628pub enum RenderStatus {
629    #[num_enum(default)]
630    Completed = nsi_sys::NSIStoppingStatus::RenderCompleted as _,
631    Aborted = nsi_sys::NSIStoppingStatus::RenderAborted as _,
632    Synchronized = nsi_sys::NSIStoppingStatus::RenderSynchronized as _,
633    Restarted = nsi_sys::NSIStoppingStatus::RenderRestarted as _,
634}
635
636/// A closure which is called to inform about the status of an ongoing render.
637///
638/// It is passed to ɴsɪ via [`render_control()`](Context::render_control())’s
639/// `"callback"` argument.
640///
641/// # Examples
642///
643/// ```
644/// # use nsi_core as nsi;
645/// # let ctx = nsi::Context::new(None).unwrap();
646/// let status_callback = nsi::context::StatusCallback::new(
647///     |_: &nsi::context::Context, status: nsi::context::RenderStatus| {
648///         println!("Status: {:?}", status);
649///     },
650/// );
651///
652/// ctx.render_control(
653///     nsi::Action::Start,
654///     Some(&[nsi::callback!("callback", status_callback)]),
655/// );
656/// ```
657pub trait FnStatus<'a>: Fn(
658    // The [`Context`] for which this closure was called.
659    &Context,
660    // Status of interactive render session.
661    RenderStatus,
662)
663+ 'a {}
664
665#[doc(hidden)]
666impl<
667        'a,
668        T: Fn(&Context, RenderStatus)
669            + 'a
670            + for<'r, 's> Fn(&'r context::Context<'s>, RenderStatus),
671    > FnStatus<'a> for T
672{
673}
674
675// FIXME once trait aliases are in stable.
676/*
677trait FnStatus<'a> = FnMut(
678    // Status of interactive render session.
679    status: RenderStatus
680    )
681    + 'a
682*/
683
684/// Wrapper to pass a [`FnStatus`] closure to a [`Context`].
685pub struct StatusCallback<'a>(Box<Box<dyn FnStatus<'a>>>);
686
687unsafe impl Send for StatusCallback<'static> {}
688unsafe impl Sync for StatusCallback<'static> {}
689
690impl<'a> StatusCallback<'a> {
691    pub fn new<F>(fn_status: F) -> Self
692    where
693        F: FnStatus<'a>,
694    {
695        StatusCallback(Box::new(Box::new(fn_status)))
696    }
697}
698
699impl CallbackPtr for StatusCallback<'_> {
700    #[doc(hidden)]
701    fn to_ptr(self) -> *const core::ffi::c_void {
702        Box::into_raw(self.0) as *const _ as _
703    }
704}
705
706// Trampoline function for the FnStatus callback.
707#[no_mangle]
708pub(crate) extern "C" fn render_status(
709    payload: *mut c_void,
710    context: nsi_sys::NSIContext,
711    status: c_int,
712) {
713    if !payload.is_null() {
714        let fn_status =
715            unsafe { Box::from_raw(payload as *mut Box<dyn FnStatus>) };
716        let ctx = Context(Arc::new(InnerContext {
717            context,
718            _marker: PhantomData,
719        }));
720
721        fn_status(&ctx, status.into());
722
723        // We must not call drop() on this context.
724        // This is safe as Context doesn't allocate and this one is on the stack
725        // anyway.
726        std::mem::forget(ctx);
727    }
728}
729
730/// A closure which is called to inform about the errors during scene defintion
731/// or a render.
732///
733/// It is passed to ɴsɪ via [`new()`](Context::new())’s `"errorhandler"`
734/// argument.
735///
736/// # Examples
737///
738/// ```
739/// # use nsi_core as nsi;
740/// use log::{debug, error, info, trace, warn, Level};
741///
742/// let error_handler = nsi::ErrorCallback::new(
743///     |level: Level, message_id: i32, message: &str| match level {
744///         Level::Error => error!("[{}] {}", message_id, message),
745///         Level::Warn => warn!("[{}] {}", message_id, message),
746///         Level::Info => info!("[{}] {}", message_id, message),
747///         Level::Debug => debug!("[{}] {}", message_id, message),
748///         Level::Trace => trace!("[{}] {}", message_id, message),
749///     },
750/// );
751///
752/// let ctx = nsi::Context::new(Some(&[nsi::callback!(
753///     "errorhander",
754///     error_handler
755/// )]))
756/// .unwrap();
757///
758/// // Do something with ctx ...
759/// ```
760pub trait FnError<'a>: Fn(
761    // The error level.
762    log::Level,
763    // The message id.
764    i32,
765    // The message.
766    &str,
767)
768+ 'a {}
769
770#[doc(hidden)]
771impl<
772        'a,
773        T: Fn(log::Level, i32, &str) + 'a + for<'r> Fn(log::Level, i32, &'r str),
774    > FnError<'a> for T
775{
776}
777
778/// Wrapper to pass a [`FnError`] closure to a [`Context`].
779pub struct ErrorCallback<'a>(Box<Box<dyn FnError<'a>>>);
780
781unsafe impl Send for ErrorCallback<'static> {}
782unsafe impl Sync for ErrorCallback<'static> {}
783
784impl<'a> ErrorCallback<'a> {
785    pub fn new<F>(fn_error: F) -> Self
786    where
787        F: FnError<'a>,
788    {
789        ErrorCallback(Box::new(Box::new(fn_error)))
790    }
791}
792
793impl CallbackPtr for ErrorCallback<'_> {
794    #[doc(hidden)]
795    fn to_ptr(self) -> *const core::ffi::c_void {
796        Box::into_raw(self.0) as *const _ as _
797    }
798}
799
800// Trampoline function for the FnError callback.
801#[no_mangle]
802pub(crate) extern "C" fn error_handler(
803    payload: *mut c_void,
804    level: c_int,
805    code: c_int,
806    message: *const c_char,
807) {
808    if !payload.is_null() {
809        let fn_error =
810            unsafe { Box::from_raw(payload as *mut Box<dyn FnError>) };
811
812        let message = unsafe {
813            NullTerminatedStr::from_cstr_unchecked(CStr::from_ptr(message as _))
814        };
815
816        let level = match NSIErrorLevel::from(level) {
817            NSIErrorLevel::Message => log::Level::Trace,
818            NSIErrorLevel::Info => log::Level::Info,
819            NSIErrorLevel::Warning => log::Level::Warn,
820            NSIErrorLevel::Error => log::Level::Error,
821        };
822
823        fn_error(level, code as _, message.as_ref());
824    }
825}