Skip to main content

v_log/
lib.rs

1// Copyright 2026 redweasel. Based on the `log` crate by the Rust Project Developers Copyright 2015.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! A lightweight visual vlogging/debugging facade. Useful for geometry applications.
10//!
11//! The `v-log` crate provides a single vlogging API that abstracts over the
12//! actual vlogging implementation. Libraries can use the vlogging API provided
13//! by this crate, and the consumer of those libraries can choose the vlogging
14//! implementation that is most suitable for its use case.
15//!
16//! If no vlogging implementation is selected, the facade falls back to a "noop"
17//! implementation, which has very little overhead.
18//!
19//! A vlog request consists of a _target_, a _surface_, and a _visual_. A target is a
20//! string which defaults to the module path of the location of the vlog request,
21//! though that default may be overridden. Vlogger implementations typically use
22//! the target to filter requests based on some user configuration. A surface is
23//! a space or context in which the drawing is done. Vlogger implementations may
24//! choose different ways to represent them, but any named surface must be either
25//! filtered out or displayed by the implementation. There is no global "main-surface",
26//! as drawing surfaces/spaces are created on demand. One can think of these as
27//! plot figures or desktop windows.
28//!
29//! # Usage
30//!
31//! The basic use of the vlog crate is through the vlogging macros:
32//! [`point!`], [`polyline!`], [`message!`], [`label!`], [`clear!`].
33//! They form the basic building blocks of drawing.
34
35#![warn(missing_docs)]
36#![deny(missing_debug_implementations, unconditional_recursion)]
37#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
38
39#[cfg(all(not(feature = "std"), not(test)))]
40extern crate core as std;
41
42#[cfg(feature = "std")]
43use std::error;
44use std::fmt;
45
46#[cfg(target_has_atomic = "ptr")]
47use std::sync::atomic::{AtomicUsize, Ordering};
48
49#[cfg(not(target_has_atomic = "ptr"))]
50use std::cell::Cell;
51#[cfg(not(target_has_atomic = "ptr"))]
52use std::sync::atomic::Ordering;
53
54#[macro_use]
55mod macros;
56#[doc(hidden)]
57pub mod __private_api;
58
59#[cfg(not(target_has_atomic = "ptr"))]
60struct AtomicUsize {
61    v: Cell<usize>,
62}
63
64#[cfg(not(target_has_atomic = "ptr"))]
65impl AtomicUsize {
66    const fn new(v: usize) -> AtomicUsize {
67        AtomicUsize { v: Cell::new(v) }
68    }
69
70    fn load(&self, _order: Ordering) -> usize {
71        self.v.get()
72    }
73
74    fn store(&self, val: usize, _order: Ordering) {
75        self.v.set(val)
76    }
77}
78
79// Any platform without atomics is unlikely to have multiple cores, so
80// writing via Cell will not be a race condition.
81#[cfg(not(target_has_atomic = "ptr"))]
82unsafe impl Sync for AtomicUsize {}
83
84// The VLOGGER static holds a pointer to the global vlogger. It is protected by
85// the STATE static which determines whether VLOGGER has been initialized yet.
86static mut VLOGGER: &dyn VLog = &NopVLogger;
87
88static STATE: AtomicUsize = AtomicUsize::new(0);
89
90// There are three different states that we care about: the vlogger's
91// uninitialized, the vlogger's initializing (set_vlogger's been called but
92// VLOGGER hasn't actually been set yet), or the vlogger's active.
93const UNINITIALIZED: usize = 0;
94const INITIALIZING: usize = 1;
95const INITIALIZED: usize = 2;
96
97static SET_VLOGGER_ERROR: &str = "attempted to set a vlogger after the vlogging system \
98                                 was already initialized";
99
100#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
101enum MaybeStaticStr<'a> {
102    Static(&'static str),
103    Borrowed(&'a str),
104}
105
106impl<'a> MaybeStaticStr<'a> {
107    #[inline]
108    fn get(&self) -> &'a str {
109        match *self {
110            MaybeStaticStr::Static(s) => s,
111            MaybeStaticStr::Borrowed(s) => s,
112        }
113    }
114}
115
116/// The "payload" of a vlog command.
117///
118/// # Use
119///
120/// `Record` structures are passed as parameters to the [`vlog`][VLog::vlog]
121/// method of the [`VLog`] trait. Vlogger implementors manipulate these
122/// structures in order to display vlog commands. `Record`s are automatically
123/// created by the macros and so are not seen by vlog users.
124#[derive(Clone, Debug)]
125pub struct Record<'a> {
126    metadata: Metadata<'a>,
127    visual: Visual,
128    color: Color,
129    size: f64,
130    args: fmt::Arguments<'a>,
131    module_path: Option<MaybeStaticStr<'a>>,
132    file: Option<MaybeStaticStr<'a>>,
133    line: Option<u32>,
134}
135
136impl<'a> Record<'a> {
137    /// Returns a new builder.
138    #[inline]
139    pub fn builder() -> RecordBuilder<'a> {
140        RecordBuilder::new()
141    }
142
143    /// The message/label text.
144    #[inline]
145    pub fn args(&self) -> &fmt::Arguments<'a> {
146        &self.args
147    }
148
149    /// The visual element to draw.
150    #[inline]
151    pub fn visual(&self) -> &Visual {
152        &self.visual
153    }
154
155    /// The color of the visual element.
156    #[inline]
157    pub fn color(&self) -> &Color {
158        &self.color
159    }
160
161    /// The size of the visual element.
162    #[inline]
163    pub fn size(&self) -> f64 {
164        self.size
165    }
166
167    /// Metadata about the vlog directive.
168    #[inline]
169    pub fn metadata(&self) -> &Metadata<'a> {
170        &self.metadata
171    }
172
173    /// The name of the target of the directive.
174    #[inline]
175    pub fn target(&self) -> &'a str {
176        self.metadata.target()
177    }
178
179    /// The name of the surface of the directive.
180    #[inline]
181    pub fn surface(&self) -> &'a str {
182        self.metadata.surface()
183    }
184
185    /// The module path of the message.
186    #[inline]
187    pub fn module_path(&self) -> Option<&'a str> {
188        self.module_path.map(|s| s.get())
189    }
190
191    /// The module path of the message, if it is a `'static` string.
192    #[inline]
193    pub fn module_path_static(&self) -> Option<&'static str> {
194        match self.module_path {
195            Some(MaybeStaticStr::Static(s)) => Some(s),
196            _ => None,
197        }
198    }
199
200    /// The source file containing the message.
201    #[inline]
202    pub fn file(&self) -> Option<&'a str> {
203        self.file.map(|s| s.get())
204    }
205
206    /// The source file containing the message, if it is a `'static` string.
207    #[inline]
208    pub fn file_static(&self) -> Option<&'static str> {
209        match self.file {
210            Some(MaybeStaticStr::Static(s)) => Some(s),
211            _ => None,
212        }
213    }
214
215    /// The line containing the message.
216    #[inline]
217    pub fn line(&self) -> Option<u32> {
218        self.line
219    }
220}
221
222/// Builder for [`Record`](struct.Record.html).
223///
224/// Typically should only be used by vlog library creators or for testing and "shim vloggers".
225/// The `RecordBuilder` can set the different parameters of `Record` object, and returns
226/// the created object when `build` is called.
227///
228/// # Examples
229///
230/// ```
231/// use v_log::{Record, Visual, Color};
232///
233/// let record = Record::builder()
234///                 .args(format_args!("Error!"))
235///                 .target("myApp")
236///                 .surface("AppSurface")
237///                 .visual(Visual::Message)
238///                 .color(Color::Healthy)
239///                 .file(Some("server.rs"))
240///                 .line(Some(144))
241///                 .module_path(Some("server"))
242///                 .build();
243/// ```
244///
245/// Alternatively, use [`MetadataBuilder`](struct.MetadataBuilder.html):
246///
247/// ```
248/// use v_log::{Record, Visual, Color, MetadataBuilder};
249///
250/// let error_metadata = MetadataBuilder::new()
251///                         .target("myApp")
252///                         .surface("AppSurface")
253///                         .build();
254///
255/// let record = Record::builder()
256///                 .metadata(error_metadata)
257///                 .args(format_args!("Error!"))
258///                 .visual(Visual::Message)
259///                 .color(Color::Healthy)
260///                 .line(Some(433))
261///                 .file(Some("app.rs"))
262///                 .module_path(Some("server"))
263///                 .build();
264/// ```
265#[derive(Debug)]
266pub struct RecordBuilder<'a> {
267    record: Record<'a>,
268}
269
270impl<'a> RecordBuilder<'a> {
271    /// Construct new `RecordBuilder`.
272    ///
273    /// The default options are:
274    ///
275    /// - `visual`: [`Visual::Message`]
276    /// - `color`: [`Color::Base`]
277    /// - `size`: `12.0`
278    /// - `args`: [`format_args!("")`]
279    /// - `metadata`: [`Metadata::builder().build()`]
280    /// - `module_path`: `None`
281    /// - `file`: `None`
282    /// - `line`: `None`
283    ///
284    /// [`format_args!("")`]: https://doc.rust-lang.org/std/macro.format_args.html
285    /// [`Metadata::builder().build()`]: struct.MetadataBuilder.html#method.build
286    #[inline]
287    pub fn new() -> RecordBuilder<'a> {
288        RecordBuilder {
289            record: Record {
290                visual: Visual::Message,
291                color: Color::Base,
292                size: 12.0,
293                args: format_args!(""),
294                metadata: Metadata::builder().build(),
295                module_path: None,
296                file: None,
297                line: None,
298            },
299        }
300    }
301
302    /// Set [`visual`](struct.Record.html#method.visual).
303    pub fn visual(&mut self, visual: Visual) -> &mut RecordBuilder<'a> {
304        self.record.visual = visual;
305        self
306    }
307
308    /// Set [`color`](struct.Record.html#method.color).
309    pub fn color(&mut self, color: Color) -> &mut RecordBuilder<'a> {
310        self.record.color = color;
311        self
312    }
313
314    /// Set [`size`](struct.Record.html#method.size).
315    pub fn size(&mut self, size: f64) -> &mut RecordBuilder<'a> {
316        self.record.size = size;
317        self
318    }
319
320    /// Set [`args`](struct.Record.html#method.args).
321    #[inline]
322    pub fn args(&mut self, args: fmt::Arguments<'a>) -> &mut RecordBuilder<'a> {
323        self.record.args = args;
324        self
325    }
326
327    /// Set [`metadata`](struct.Record.html#method.metadata). Construct a `Metadata` object with [`MetadataBuilder`](struct.MetadataBuilder.html).
328    #[inline]
329    pub fn metadata(&mut self, metadata: Metadata<'a>) -> &mut RecordBuilder<'a> {
330        self.record.metadata = metadata;
331        self
332    }
333
334    /// Set [`Metadata::surface`](struct.Metadata.html#method.surface).
335    #[inline]
336    pub fn surface(&mut self, surface: &'a str) -> &mut RecordBuilder<'a> {
337        self.record.metadata.surface = surface;
338        self
339    }
340
341    /// Set [`Metadata::target`](struct.Metadata.html#method.target)
342    #[inline]
343    pub fn target(&mut self, target: &'a str) -> &mut RecordBuilder<'a> {
344        self.record.metadata.target = target;
345        self
346    }
347
348    /// Set [`module_path`](struct.Record.html#method.module_path)
349    #[inline]
350    pub fn module_path(&mut self, path: Option<&'a str>) -> &mut RecordBuilder<'a> {
351        self.record.module_path = path.map(MaybeStaticStr::Borrowed);
352        self
353    }
354
355    /// Set [`module_path`](struct.Record.html#method.module_path) to a `'static` string
356    #[inline]
357    pub fn module_path_static(&mut self, path: Option<&'static str>) -> &mut RecordBuilder<'a> {
358        self.record.module_path = path.map(MaybeStaticStr::Static);
359        self
360    }
361
362    /// Set [`file`](struct.Record.html#method.file)
363    #[inline]
364    pub fn file(&mut self, file: Option<&'a str>) -> &mut RecordBuilder<'a> {
365        self.record.file = file.map(MaybeStaticStr::Borrowed);
366        self
367    }
368
369    /// Set [`file`](struct.Record.html#method.file) to a `'static` string.
370    #[inline]
371    pub fn file_static(&mut self, file: Option<&'static str>) -> &mut RecordBuilder<'a> {
372        self.record.file = file.map(MaybeStaticStr::Static);
373        self
374    }
375
376    /// Set [`line`](struct.Record.html#method.line)
377    #[inline]
378    pub fn line(&mut self, line: Option<u32>) -> &mut RecordBuilder<'a> {
379        self.record.line = line;
380        self
381    }
382
383    /// Invoke the builder and return a `Record`
384    #[inline]
385    pub fn build(&self) -> Record<'a> {
386        self.record.clone()
387    }
388}
389
390impl Default for RecordBuilder<'_> {
391    fn default() -> Self {
392        Self::new()
393    }
394}
395
396/// Metadata about a vlog command.
397///
398/// # Use
399///
400/// `Metadata` structs are created when users of the library use
401/// vlogging macros.
402///
403/// They are consumed by implementations of the `VLog` trait in the
404/// `enabled` method.
405///
406/// Users should use the `vlog_enabled!` macro in their code to avoid
407/// constructing expensive vlog messages.
408#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
409pub struct Metadata<'a> {
410    surface: &'a str,
411    target: &'a str,
412}
413
414impl<'a> Metadata<'a> {
415    /// Returns a new builder.
416    #[inline]
417    pub fn builder() -> MetadataBuilder<'a> {
418        MetadataBuilder::new()
419    }
420
421    /// The surface to draw on.
422    #[inline]
423    pub fn surface(&self) -> &'a str {
424        self.surface
425    }
426
427    /// The name of the target of the directive.
428    #[inline]
429    pub fn target(&self) -> &'a str {
430        self.target
431    }
432}
433
434/// Builder for [`Metadata`](struct.Metadata.html).
435///
436/// Typically should only be used by vlog library creators or for testing and "shim vloggers".
437/// The `MetadataBuilder` can set the different parameters of a `Metadata` object, and returns
438/// the created object when `build` is called.
439///
440/// # Example
441///
442/// ```
443/// let target = "myApp";
444/// let surface = "AppSurface";
445/// use v_log::MetadataBuilder;
446/// let metadata = MetadataBuilder::new()
447///                     .surface(surface)
448///                     .target(target)
449///                     .build();
450/// ```
451#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
452pub struct MetadataBuilder<'a> {
453    metadata: Metadata<'a>,
454}
455
456impl<'a> MetadataBuilder<'a> {
457    /// Construct a new `MetadataBuilder`.
458    ///
459    /// The default options are:
460    ///
461    /// - `surface`: `""`
462    /// - `target`: `""`
463    #[inline]
464    pub fn new() -> MetadataBuilder<'a> {
465        MetadataBuilder {
466            metadata: Metadata {
467                surface: "",
468                target: "",
469            },
470        }
471    }
472
473    /// Setter for [`surface`](struct.Metadata.html#method.surface).
474    #[inline]
475    pub fn surface(&mut self, surface: &'a str) -> &mut MetadataBuilder<'a> {
476        self.metadata.surface = surface;
477        self
478    }
479
480    /// Setter for [`target`](struct.Metadata.html#method.target).
481    #[inline]
482    pub fn target(&mut self, target: &'a str) -> &mut MetadataBuilder<'a> {
483        self.metadata.target = target;
484        self
485    }
486
487    /// Returns a `Metadata` object.
488    #[inline]
489    pub fn build(&self) -> Metadata<'a> {
490        self.metadata.clone()
491    }
492}
493
494impl Default for MetadataBuilder<'_> {
495    fn default() -> Self {
496        Self::new()
497    }
498}
499
500/// The style of a point type visual. There is two distinct types of styles.
501///
502/// 1. Circle with absolute size: [`FilledCircle`](`PointStyle::FilledCircle`), [`Circle`](`PointStyle::Circle`), [`DashedCircle`](`PointStyle::DashedCircle`), [`FilledSquare`](`PointStyle::FilledSquare`), [`Square`](`PointStyle::Square`), [`DashedSquare`](`PointStyle::DashedSquare`).
503///    These are useful to draw circles/squares with a fixed size. In a 3D context these represent spheres/cubes instead.
504///    The circle outline uses the correct sphere outline in the used view projection, which means they become
505///    ellipses/hyperbolas in a perspective projection. The outlined cube is preferrably drawn as a wireframe cube.
506/// 2. Point billboard marker where the size is determined in screen coordinates instead of the same space as the position coordinates.
507///    Zooming in the view will not change their apparent size. These are useful to mark points.
508#[derive(Clone, Copy, Debug)]
509#[non_exhaustive]
510pub enum PointStyle {
511    /* 2D/3D objects */
512    /// A filled circle/sphere. [`size`](struct.Record.html#method.size) is the diameter.
513    FilledCircle,
514    /// A circle/sphere outline. [`size`](struct.Record.html#method.size) is the diameter.
515    Circle,
516    /// A dashed circle/sphere outline. [`size`](struct.Record.html#method.size) is the diameter.
517    DashedCircle,
518    /// A filled square/cube. [`size`](struct.Record.html#method.size) is the width.
519    FilledSquare,
520    /// A square/cube outline/wireframe. [`size`](struct.Record.html#method.size) is the width.
521    Square,
522    /// A dashed square/cube outline/wireframe. [`size`](struct.Record.html#method.size) is the width.
523    DashedSquare,
524
525    /* 2D markers */
526    /// A filled circle. Dynamically scaled so the size is the pixel size.
527    Point,
528    /// A circle outline. Dynamically scaled so the size is the pixel size.
529    PointOutline,
530    /// A filled square. Dynamically scaled so the size is the pixel size.
531    PointSquare,
532    /// A square outline. Dynamically scaled so the size is the pixel size.
533    PointSquareOutline,
534    /// An `x` marker. Dynamically scaled so the size is the pixel size.
535    PointCross,
536    /// A filled diamond. Dynamically scaled so the size is the pixel size.
537    PointDiamond,
538    /// A diamond outline. Dynamically scaled so the size is the pixel size.
539    PointDiamondOutline,
540}
541
542/// The style of a line type visual.
543#[derive(Clone, Copy, Debug)]
544#[non_exhaustive]
545pub enum LineStyle {
546    /// A simple straight continuous line
547    Simple,
548    /// A dashed line
549    Dashed,
550    /// A line with an arrowhead on the second point.
551    Arrow,
552    /// A line with half an arrowhead on the second point or along the line. If a polygon is drawn in CCW point order, the harpoon will be on the inside.
553    InsideHarpoonCCW,
554    /// A line with half an arrowhead on the second point or along the line. If a polygon is drawn in CW point order, the harpoon will be on the inside.
555    InsideHarpoonCW,
556}
557
558/// The text alignment relative to a specified spacepoint.
559/// All variants center the text vertically.
560#[derive(Clone, Copy, Debug, Default)]
561#[repr(u8)]
562pub enum TextAlignment {
563    /// Align the left side of the text to the position. Vertically centered.
564    Left = 0,
565    /// Center the text on the position.
566    Center = 1,
567    /// Align the right side of the text to the position. Vertically centered.
568    Right = 2,
569    /// Center the text on the position if possible, but the vlogger is allowed
570    /// to shift the text by a small amount for better readability.
571    #[default]
572    Flexible = 3,
573}
574
575/// A visual element to be drawn by the vlogger.
576#[derive(Clone, Debug, Default)]
577pub enum Visual {
578    /// Just a vlog message to be shown in the vlogger instead of the regular vlogs.
579    #[default]
580    Message,
581    /// A text label placed in space with the message string.
582    Label {
583        /// The spacepoint x-coordinate
584        x: f64,
585        /// The spacepoint y-coordinate
586        y: f64,
587        /// The spacepoint z-coordinate for 3D visualisations.
588        z: f64,
589        /// The alignment of the text relative to the spacepoint.
590        alignment: TextAlignment,
591    },
592    /// A circle/point placed in space.
593    Point {
594        /// The spacepoint x-coordinate
595        x: f64,
596        /// The spacepoint y-coordinate
597        y: f64,
598        /// The spacepoint z-coordinate for 3D visualisations.
599        z: f64,
600        /// The drawing style of the circle/point.
601        style: PointStyle,
602    },
603    /// A line placed in space.
604    Line {
605        /// The 1. spacepoint x-coordinate
606        x1: f64,
607        /// The 1. spacepoint y-coordinate
608        y1: f64,
609        /// The 1. spacepoint z-coordinate for 3D visualisations.
610        z1: f64,
611        /// The 2. spacepoint x-coordinate
612        x2: f64,
613        /// The 2. spacepoint y-coordinate
614        y2: f64,
615        /// The 2. spacepoint z-coordinate for 3D visualisations.
616        z2: f64,
617        /// The drawing style of the line.
618        style: LineStyle,
619    },
620}
621
622/// Basic debugging theme colors.
623#[derive(Clone, Copy, Debug, Default)]
624#[non_exhaustive]
625pub enum Color {
626    /// Base line color. E.g. white on black background.
627    #[default]
628    Base,
629    /// Some shade of green.
630    Healthy,
631    /// Some shade of blue.
632    Info,
633    /// Some shade of yellow.
634    Warn,
635    /// Some shade of red.
636    Error,
637    /// Some shade of red (**R**gb = **X**YZ)
638    X,
639    /// Some shade of green (r**G**b = X**Y**Z)
640    Y,
641    /// Some shade of blue (rg**B** = XY**Z**)
642    Z,
643    /// A specific color by hexcode. The MSB is red, the LSB is alpha.
644    Hex(u32),
645}
646
647/// A trait encapsulating the operations required of a vlogger.
648pub trait VLog {
649    /// Determines if a vlog command with the specified metadata would be
650    /// vlogged.
651    ///
652    /// This is used by the `vlog_enabled!` macro to allow callers to avoid
653    /// expensive computation of vlog message arguments if the message would be
654    /// discarded anyway.
655    fn enabled(&self, metadata: &Metadata) -> bool;
656    /// Draw a point or line in 3D or 2D (ignoring z or using it as z-index).
657    ///
658    /// This is only called if the vlogging of messages with the given metadata is enabled.
659    fn vlog(&self, record: &Record);
660    /// Clear a drawing surface e.g. to redraw its content.
661    fn clear(&self, surface: &str);
662}
663
664/// A dummy initial value for VLOGGER.
665struct NopVLogger;
666
667impl VLog for NopVLogger {
668    fn enabled(&self, _: &Metadata) -> bool {
669        false
670    }
671
672    fn vlog(&self, _: &Record) {}
673    fn clear(&self, _: &str) {}
674}
675
676impl<T> VLog for &'_ T
677where
678    T: ?Sized + VLog,
679{
680    fn enabled(&self, metadata: &Metadata) -> bool {
681        (**self).enabled(metadata)
682    }
683
684    fn vlog(&self, record: &Record) {
685        (**self).vlog(record);
686    }
687
688    fn clear(&self, surface: &str) {
689        (**self).clear(surface);
690    }
691}
692
693#[cfg(feature = "std")]
694impl<T> VLog for std::boxed::Box<T>
695where
696    T: ?Sized + VLog,
697{
698    fn enabled(&self, metadata: &Metadata) -> bool {
699        self.as_ref().enabled(metadata)
700    }
701
702    fn vlog(&self, record: &Record) {
703        self.as_ref().vlog(record);
704    }
705
706    fn clear(&self, surface: &str) {
707        self.as_ref().clear(surface);
708    }
709}
710
711#[cfg(feature = "std")]
712impl<T> VLog for std::sync::Arc<T>
713where
714    T: ?Sized + VLog,
715{
716    fn enabled(&self, metadata: &Metadata) -> bool {
717        self.as_ref().enabled(metadata)
718    }
719
720    fn vlog(&self, record: &Record) {
721        self.as_ref().vlog(record);
722    }
723
724    fn clear(&self, surface: &str) {
725        self.as_ref().clear(surface);
726    }
727}
728
729/// Sets the global vlogger to a `Box<VLog>`.
730///
731/// This is a simple convenience wrapper over `set_vlogger`, which takes a
732/// `Box<VLog>` rather than a `&'static VLog`. See the documentation for
733/// [`set_vlogger`] for more details.
734///
735/// Requires the `std` feature.
736///
737/// # Errors
738///
739/// An error is returned if a vlogger has already been set.
740///
741/// [`set_vlogger`]: fn.set_vlogger.html
742#[cfg(all(feature = "std", target_has_atomic = "ptr"))]
743pub fn set_boxed_vlogger(vlogger: Box<dyn VLog>) -> Result<(), SetVLoggerError> {
744    set_vlogger_inner(|| Box::leak(vlogger))
745}
746
747/// Sets the global vlogger to a `&'static VLog`.
748///
749/// This function may only be called once in the lifetime of a program. Any vlog
750/// events that occur before the call to `set_vlogger` completes will be ignored.
751///
752/// This function does not typically need to be called manually. VLogger
753/// implementations should provide an initialization method that installs the
754/// vlogger internally.
755///
756/// # Availability
757///
758/// This method is available even when the `std` feature is disabled. However,
759/// it is currently unavailable on `thumbv6` targets, which lack support for
760/// some atomic operations which are used by this function. Even on those
761/// targets, [`set_vlogger_racy`] will be available.
762///
763/// # Errors
764///
765/// An error is returned if a vlogger has already been set.
766///
767/// # Examples
768///
769/// ```ignore
770/// use v_log::{message, Record, Metadata};
771///
772/// static MY_VLOGGER: MyVLogger = MyVLogger;
773///
774/// struct MyVLogger;
775///
776/// impl v_log::VLog for MyVLogger {...}
777///
778/// # fn main(){
779/// v_log::set_vlogger(&MY_VLOGGER).unwrap();
780///
781/// message!("hello vlog");
782/// # }
783/// ```
784///
785/// [`set_vlogger_racy`]: fn.set_vlogger_racy.html
786#[cfg(target_has_atomic = "ptr")]
787pub fn set_vlogger(vlogger: &'static dyn VLog) -> Result<(), SetVLoggerError> {
788    set_vlogger_inner(|| vlogger)
789}
790
791#[cfg(target_has_atomic = "ptr")]
792fn set_vlogger_inner<F>(make_vlogger: F) -> Result<(), SetVLoggerError>
793where
794    F: FnOnce() -> &'static dyn VLog,
795{
796    match STATE.compare_exchange(
797        UNINITIALIZED,
798        INITIALIZING,
799        Ordering::Acquire,
800        Ordering::Relaxed,
801    ) {
802        Ok(UNINITIALIZED) => {
803            unsafe {
804                VLOGGER = make_vlogger();
805            }
806            STATE.store(INITIALIZED, Ordering::Release);
807            Ok(())
808        }
809        Err(INITIALIZING) => {
810            while STATE.load(Ordering::Relaxed) == INITIALIZING {
811                std::hint::spin_loop();
812            }
813            Err(SetVLoggerError(()))
814        }
815        _ => Err(SetVLoggerError(())),
816    }
817}
818
819/// A thread-unsafe version of [`set_vlogger`].
820///
821/// This function is available on all platforms, even those that do not have
822/// support for atomics that is needed by [`set_vlogger`].
823///
824/// In almost all cases, [`set_vlogger`] should be preferred.
825///
826/// # Safety
827///
828/// This function is only safe to call when it cannot race with any other
829/// calls to `set_vlogger` or `set_vlogger_racy`.
830///
831/// This can be upheld by (for example) making sure that **there are no other
832/// threads**, and (on embedded) that **interrupts are disabled**.
833///
834/// It is safe to use other vlogging functions while this function runs
835/// (including all vlogging macros).
836///
837/// [`set_vlogger`]: fn.set_vlogger.html
838pub unsafe fn set_vlogger_racy(vlogger: &'static dyn VLog) -> Result<(), SetVLoggerError> {
839    match STATE.load(Ordering::Acquire) {
840        UNINITIALIZED => {
841            unsafe {
842                VLOGGER = vlogger;
843            }
844            STATE.store(INITIALIZED, Ordering::Release);
845            Ok(())
846        }
847        INITIALIZING => {
848            // This is just plain UB, since we were racing another initialization function
849            unreachable!("set_vlogger_racy must not be used with other initialization functions")
850        }
851        _ => Err(SetVLoggerError(())),
852    }
853}
854
855/// The type returned by [`set_vlogger`] if [`set_vlogger`] has already been called.
856///
857/// [`set_vlogger`]: fn.set_vlogger.html
858#[allow(missing_copy_implementations)]
859#[derive(Debug)]
860pub struct SetVLoggerError(());
861
862impl fmt::Display for SetVLoggerError {
863    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
864        fmt.write_str(SET_VLOGGER_ERROR)
865    }
866}
867
868// The Error trait is not available in libcore
869#[cfg(feature = "std")]
870impl error::Error for SetVLoggerError {}
871
872/// Returns a reference to the vlogger.
873///
874/// If a vlogger has not been set, a no-op implementation is returned.
875pub fn vlogger() -> &'static dyn VLog {
876    // Acquire memory ordering guarantees that current thread would see any
877    // memory writes that happened before store of the value
878    // into `STATE` with memory ordering `Release` or stronger.
879    //
880    // Since the value `INITIALIZED` is written only after `VLOGGER` was
881    // initialized, observing it after `Acquire` load here makes both
882    // write to the `VLOGGER` static and initialization of the vlogger
883    // internal state synchronized with current thread.
884    if STATE.load(Ordering::Acquire) != INITIALIZED {
885        static NOP: NopVLogger = NopVLogger;
886        &NOP
887    } else {
888        unsafe { VLOGGER }
889    }
890}