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}