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