add tracing logging

This commit is contained in:
Franz Dietrich 2025-10-12 13:22:21 +02:00
parent 62e87edd4d
commit c96d66247e
7 changed files with 228 additions and 84 deletions

View File

@ -12,9 +12,10 @@ A modern turtle graphics library for Rust built on [Macroquad](https://macroquad
- ⚡ **Smooth Animations**: Tweening support with easing functions and live fill preview
- 🚀 **Instant Mode**: Execute commands immediately without animation (speed ≥ 999)
- 🎯 **High-Quality Rendering**: Complete Lyon tessellation pipeline with GPU acceleration
- 🕳️ **Multi-Contour Fills**: Automatic hole detection with EvenOdd fill rule - draw cheese with holes!
- **Multi-Contour Fills**: Automatic hole detection with EvenOdd fill rule - draw cheese with holes!
- 📐 **Self-Intersecting Paths**: Stars, complex shapes - all handled correctly
- 🐢 **Multiple Turtle Shapes**: Triangle, classic turtle, circle, square, arrow, and custom shapes
- 🔍 **Structured Logging**: Optional `tracing` integration for debugging (zero overhead when disabled)
- 💨 **Lightweight**: Fast compilation and runtime
## Quick Start
@ -149,6 +150,33 @@ loop {
}
```
## Debugging and Logging
The library uses [`tracing`](https://docs.rs/tracing) for structured diagnostic logging. This is completely optional - if you don't set up a subscriber, there's zero overhead.
### Enable Logging
```rust
// Add to your Cargo.toml:
// tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
```
Control verbosity with the `RUST_LOG` environment variable:
```bash
# Show debug output
RUST_LOG=turtle_lib_macroquad=debug cargo run
# Very verbose trace output
RUST_LOG=turtle_lib_macroquad=trace cargo run
```
**See the complete example**: [`examples/logging_example.rs`](turtle-lib-macroquad/examples/logging_example.rs) demonstrates initialization, log levels, filtering, and example output.
## Examples
Run examples with:
@ -161,6 +189,10 @@ cargo run --example yinyang
cargo run --example stern
cargo run --example nikolaus
# Logging example - shows how to enable debug output
cargo run --example logging_example
RUST_LOG=turtle_lib_macroquad=debug cargo run --example logging_example
# Lyon proof-of-concept examples
cargo run --package turtle-lyon-poc --example yinyang --release
cargo run --package turtle-lyon-poc --example basic_shapes --release
@ -184,6 +216,9 @@ cargo run --package turtle-lyon-poc --example fill_comparison --release
- **fill_circle_test.rs**: Circle fills with different angles
- **fill_instant_test.rs**: Quick fill test in instant mode
#### Debugging
- **logging_example.rs**: Demonstrates how to enable and use tracing/logging output
## Why Lyon?
- Automatic hole detection via EvenOdd fill rule

View File

@ -8,6 +8,8 @@ license = "MIT OR Apache-2.0"
macroquad = "0.4"
tween = "2.1.0"
lyon = "1.0"
tracing = { version = "0.1", features = ["log"], default-features = false }
[dev-dependencies]
# For examples and testing
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }

View File

@ -0,0 +1,97 @@
//! Example demonstrating how to enable logging/tracing output from the turtle library
//!
//! This example shows how to use `tracing-subscriber` to see debug output from the library.
//! You can control the log level using the `RUST_LOG` environment variable:
//!
//! ```bash
//! # Show all debug output from turtle-lib-macroquad
//! RUST_LOG=turtle_lib_macroquad=debug cargo run --example logging_example
//!
//! # Show only warnings and errors
//! RUST_LOG=turtle_lib_macroquad=warn cargo run --example logging_example
//!
//! # Show trace-level output (very verbose, includes all vertices)
//! RUST_LOG=turtle_lib_macroquad=trace cargo run --example logging_example
//!
//! # Show debug output from specific modules
//! RUST_LOG=turtle_lib_macroquad::tessellation=debug cargo run --example logging_example
//! RUST_LOG=turtle_lib_macroquad::execution=debug cargo run --example logging_example
//! ```
use macroquad::prelude::*;
use turtle_lib_macroquad::*;
#[macroquad::main("Turtle Logging Example")]
async fn main() {
// Initialize tracing subscriber to see debug output
// This will respect the RUST_LOG environment variable
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| {
// Default to showing info-level logs if RUST_LOG is not set
tracing_subscriber::EnvFilter::new("turtle_lib_macroquad=info")
}),
)
.with_target(true) // Show which module the log came from
.with_thread_ids(false)
.with_line_number(true) // Show line numbers
.with_file(false)
.init();
tracing::info!("Starting turtle graphics example with logging enabled");
tracing::info!("Try running with: RUST_LOG=turtle_lib_macroquad=debug cargo run --example logging_example");
// Create a turtle plan with fill operations to see detailed logging
let mut t = create_turtle();
t.set_speed(900);
// Draw a yin-yang symbol with fills (generates lots of debug output)
t.circle_left(90.0, 180.0, 36);
t.begin_fill();
t.circle_left(90.0, 180.0, 36);
t.circle_left(45.0, 180.0, 26);
t.circle_right(45.0, 180.0, 26);
t.pen_up();
t.right(90.0);
t.forward(37.0);
t.left(90.0);
t.pen_down();
t.circle_right(8.0, 360.0, 12);
t.pen_up();
t.right(90.0);
t.forward(90.0);
t.left(90.0);
t.pen_down();
t.circle_right(8.0, 360.0, 12);
t.end_fill();
tracing::info!("Turtle plan created, starting animation");
// Set animation speed
t.set_speed(100); // Slow animation to see the logs in real-time
// Create turtle app
let mut app = TurtleApp::new().with_commands(t.build());
// Main loop
loop {
clear_background(WHITE);
// Update and render - this is where you'll see debug logs
app.update();
app.render();
// Exit when animation is complete
if app.is_complete() {
tracing::info!("Animation complete, press any key to exit");
if is_key_pressed(KeyCode::Space) {
break;
}
}
next_frame().await
}
tracing::info!("Example finished");
}

View File

@ -229,9 +229,9 @@ pub(crate) fn render_world_with_tween(
draw_mesh(&mesh_data.to_mesh());
}
Err(e) => {
eprintln!(
"#### Lyon multi-contour tessellation error for fill preview: {:?}",
e
tracing::error!(
error = ?e,
"Lyon multi-contour tessellation error for fill preview"
);
}
}

View File

@ -86,7 +86,7 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
state.pen_down = false;
// Close current contour if filling
if state.filling.is_some() {
eprintln!("PenUp: Closing current contour");
tracing::debug!("PenUp: Closing current contour");
}
state.close_fill_contour();
}
@ -95,9 +95,10 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
state.pen_down = true;
// Start new contour if filling
if state.filling.is_some() {
eprintln!(
"PenDown: Starting new contour at position ({}, {})",
state.position.x, state.position.y
tracing::debug!(
x = state.position.x,
y = state.position.y,
"PenDown: Starting new contour"
);
}
state.start_fill_contour();
@ -157,11 +158,11 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
TurtleCommand::BeginFill => {
if state.filling.is_some() {
eprintln!("Warning: begin_fill() called while already filling");
tracing::warn!("begin_fill() called while already filling");
}
let fill_color = state.fill_color.unwrap_or_else(|| {
eprintln!("Warning: No fill_color set, using black");
tracing::warn!("No fill_color set, using black");
BLACK
});
@ -176,10 +177,11 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
}
// Debug output
eprintln!("=== EndFill Debug ===");
eprintln!("Total contours: {}", fill_state.contours.len());
let span = tracing::debug_span!("end_fill", contours = fill_state.contours.len());
let _enter = span.enter();
for (i, contour) in fill_state.contours.iter().enumerate() {
eprintln!(" Contour {}: {} vertices", i, contour.len());
tracing::debug!(contour_idx = i, vertices = contour.len(), "Contour info");
}
// Create fill command - Lyon will handle EvenOdd automatically with multiple contours
@ -188,17 +190,17 @@ pub fn execute_command(command: &TurtleCommand, state: &mut TurtleState, world:
&fill_state.contours,
fill_state.fill_color,
) {
eprintln!(
"Successfully tessellated {} contours",
fill_state.contours.len()
tracing::debug!(
contours = fill_state.contours.len(),
"Successfully tessellated contours"
);
world.add_command(DrawCommand::Mesh(mesh_data));
} else {
eprintln!("ERROR: Failed to tessellate contours!");
tracing::error!("Failed to tessellate contours");
}
}
} else {
eprintln!("Warning: end_fill() called without begin_fill()");
tracing::warn!("end_fill() called without begin_fill()");
}
}
}

View File

@ -78,15 +78,15 @@ impl TurtleState {
pub fn record_fill_vertex(&mut self) {
if let Some(ref mut fill_state) = self.filling {
if self.pen_down {
eprintln!(
" [FILL] Adding vertex ({:.2}, {:.2}) to current contour (now {} vertices)",
self.position.x,
self.position.y,
fill_state.current_contour.len() + 1
tracing::trace!(
x = self.position.x,
y = self.position.y,
vertices = fill_state.current_contour.len() + 1,
"Adding vertex to current contour"
);
fill_state.current_contour.push(self.position);
} else {
eprintln!(" [FILL] Skipping vertex (pen is up)");
tracing::trace!("Skipping vertex (pen is up)");
}
}
}
@ -94,42 +94,37 @@ impl TurtleState {
/// Close the current contour and prepare for a new one (called on pen_up)
pub fn close_fill_contour(&mut self) {
if let Some(ref mut fill_state) = self.filling {
eprintln!(
" close_fill_contour called: current_contour has {} vertices",
fill_state.current_contour.len()
tracing::debug!(
vertices = fill_state.current_contour.len(),
"close_fill_contour called"
);
// Only close if we have vertices in current contour
if fill_state.current_contour.len() >= 2 {
eprintln!(
" Closing contour with {} vertices",
fill_state.current_contour.len()
);
eprintln!(
" First: ({:.2}, {:.2})",
fill_state.current_contour[0].x, fill_state.current_contour[0].y
);
eprintln!(
" Last: ({:.2}, {:.2})",
fill_state.current_contour[fill_state.current_contour.len() - 1].x,
fill_state.current_contour[fill_state.current_contour.len() - 1].y
tracing::debug!(
vertices = fill_state.current_contour.len(),
first_x = fill_state.current_contour[0].x,
first_y = fill_state.current_contour[0].y,
last_x = fill_state.current_contour[fill_state.current_contour.len() - 1].x,
last_y = fill_state.current_contour[fill_state.current_contour.len() - 1].y,
"Closing contour"
);
// Move current contour to completed contours
let contour = std::mem::take(&mut fill_state.current_contour);
fill_state.contours.push(contour);
eprintln!(
" Contour moved to completed list. Total completed contours: {}",
fill_state.contours.len()
tracing::debug!(
completed_contours = fill_state.contours.len(),
"Contour moved to completed list"
);
} else if !fill_state.current_contour.is_empty() {
eprintln!(
" WARNING: Current contour only has {} vertex/vertices, not closing",
fill_state.current_contour.len()
tracing::warn!(
vertices = fill_state.current_contour.len(),
"Current contour has insufficient vertices, not closing"
);
} else {
eprintln!(" WARNING: Current contour is EMPTY, nothing to close");
tracing::warn!("Current contour is empty, nothing to close");
}
} else {
eprintln!(" close_fill_contour called but NO active fill state!");
tracing::warn!("close_fill_contour called but no active fill state");
}
}
@ -137,13 +132,11 @@ impl TurtleState {
pub fn start_fill_contour(&mut self) {
if let Some(ref mut fill_state) = self.filling {
// Start new contour at current position
eprintln!(
" Starting NEW contour at ({:.2}, {:.2})",
self.position.x, self.position.y
);
eprintln!(
" Previous contour had {} completed contours",
fill_state.contours.len()
tracing::debug!(
x = self.position.x,
y = self.position.y,
completed_contours = fill_state.contours.len(),
"Starting new contour"
);
fill_state.current_contour = vec![self.position];
}
@ -165,8 +158,14 @@ impl TurtleState {
// Sample points along the arc based on steps
let num_samples = steps as usize;
eprintln!(" [FILL ARC] Recording arc vertices: center=({:.2}, {:.2}), radius={:.2}, steps={}, num_samples={}",
center.x, center.y, radius, steps, num_samples);
tracing::trace!(
center_x = center.x,
center_y = center.y,
radius = radius,
steps = steps,
num_samples = num_samples,
"Recording arc vertices"
);
for i in 1..=num_samples {
let progress = i as f32 / num_samples as f32;
@ -183,12 +182,12 @@ impl TurtleState {
center.x + radius * current_angle.cos(),
center.y + radius * current_angle.sin(),
);
eprintln!(
" [FILL ARC] Vertex {}: ({:.2}, {:.2}) at angle {:.2}°",
i,
vertex.x,
vertex.y,
current_angle.to_degrees()
tracing::trace!(
vertex_idx = i,
x = vertex.x,
y = vertex.y,
angle_degrees = current_angle.to_degrees(),
"Arc vertex"
);
fill_state.current_contour.push(vertex);
}

View File

@ -101,25 +101,32 @@ pub fn tessellate_multi_contour(
return Err("No contours provided".into());
}
eprintln!("\n=== tessellate_multi_contour Debug ===");
eprintln!("Total contours to tessellate: {}", contours.len());
let span = tracing::debug_span!("tessellate_multi_contour", contours = contours.len());
let _enter = span.enter();
tracing::debug!("Starting multi-contour tessellation");
// Build path with multiple sub-paths (contours)
let mut builder = Path::builder();
for (idx, contour) in contours.iter().enumerate() {
if contour.is_empty() {
eprintln!("WARNING: Contour {} is empty, skipping", idx);
tracing::warn!(contour_idx = idx, "Contour is empty, skipping");
continue;
}
eprintln!("\nContour {}: {} vertices", idx, contour.len());
eprintln!(" First vertex: ({:.2}, {:.2})", contour[0].x, contour[0].y);
tracing::trace!(
contour_idx = idx,
vertices = contour.len(),
first_x = contour[0].x,
first_y = contour[0].y,
"Processing contour"
);
if contour.len() > 1 {
eprintln!(
" Last vertex: ({:.2}, {:.2})",
contour[contour.len() - 1].x,
contour[contour.len() - 1].y
tracing::trace!(
last_x = contour[contour.len() - 1].x,
last_y = contour[contour.len() - 1].y,
"Contour end vertex"
);
}
@ -128,24 +135,27 @@ pub fn tessellate_multi_contour(
for (i, v) in contour[1..].iter().enumerate() {
builder.line_to(to_lyon_point(*v));
if i < 3 || i >= contour.len() - 4 {
eprintln!(" Vertex {}: ({:.2}, {:.2})", i + 1, v.x, v.y);
tracing::trace!(vertex_idx = i + 1, x = v.x, y = v.y, "Contour vertex");
} else if i == 3 {
eprintln!(" ... ({} more vertices)", contour.len() - 7);
tracing::trace!(
omitted = contour.len() - 7,
"Additional vertices omitted from trace"
);
}
}
builder.end(true); // Close this contour
eprintln!(" Contour closed");
tracing::trace!(contour_idx = idx, "Contour closed");
}
eprintln!("\nBuilding Lyon path...");
tracing::debug!("Building Lyon path");
let path = builder.build();
eprintln!("Path built successfully");
tracing::debug!("Path built successfully");
// Tessellate with EvenOdd fill rule - overlapping areas become holes
let mut geometry: VertexBuffers<SimpleVertex, u16> = VertexBuffers::new();
let mut tessellator = FillTessellator::new();
eprintln!("Starting tessellation with EvenOdd fill rule...");
tracing::debug!("Starting tessellation with EvenOdd fill rule");
match tessellator.tessellate_path(
&path,
&FillOptions::default().with_fill_rule(FillRule::EvenOdd),
@ -154,16 +164,15 @@ pub fn tessellate_multi_contour(
}),
) {
Ok(_) => {
eprintln!("Tessellation successful!");
eprintln!(
" Generated {} vertices, {} indices",
geometry.vertices.len(),
geometry.indices.len()
tracing::debug!(
vertices = geometry.vertices.len(),
indices = geometry.indices.len(),
triangles = geometry.indices.len() / 3,
"Tessellation successful"
);
eprintln!(" Triangles: {}", geometry.indices.len() / 3);
}
Err(e) => {
eprintln!("ERROR: Tessellation failed: {}", e);
tracing::error!(error = %e, "Tessellation failed");
return Err(Box::new(e));
}
}