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}