Eliminate Per-Frame Allocation in Rendering Loop

We resolved **Issue 4.3 (Severe Per-Frame Allocation in Rendering
Loop)** by removing the intermediate `MeshData` structure and storing
`macroquad::prelude::Mesh` directly in
[`DrawCommand::Mesh`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/state.rs).

[`DrawCommand`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/state.rs)
- Dropped `Clone` and `Debug` derives from
  [`Turtle`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/state.rs#L58)
  and
  [`DrawCommand`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/state.rs#L351),
  accommodating Macroquad's `Mesh` which does not implement these
  traits.
- Changed
  [`DrawCommand::Mesh`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/state.rs#L353)
  to the tuple variant `DrawCommand::Mesh(macroquad::prelude::Mesh)`.
- Deleted `struct MeshData` and its `to_mesh` method.

[`tessellation.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tessellation.rs)
- Renamed `build_mesh_data` to
  [`build_mesh`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tessellation.rs#L35)
  returning `macroquad::prelude::Mesh { vertices, indices, texture: None
  }` directly.
- Updated
  [`tessellate_polygon`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tessellation.rs#L71),
  [`tessellate_multi_contour`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tessellation.rs#L116),
  [`tessellate_stroke`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tessellation.rs#L214),
  [`tessellate_circle`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tessellation.rs#L259),
  and
  [`tessellate_arc`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/tessellation.rs#L308)
  to return `Result<Mesh, Box<dyn std::error::Error>>`.

[`drawing.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/drawing.rs)
- In the main drawing loop
  ([`drawing.rs:36-38`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/drawing.rs#L36-L38)):
  ```rust
  DrawCommand::Mesh(mesh) => {
  draw_mesh(mesh);
  }
  ```
  Now borrows `&Mesh` directly from the command vector with **0 heap
  allocations and 0 vector clones per frame**.
- Updated fill preview ([line
  228](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/drawing.rs#L228)),
  tween arc/center indicators ([lines 309,
  329](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/drawing.rs#L309)),
  and turtle shape drawing ([line
  349](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/drawing.rs#L349))
  to pass `&mesh` directly to `draw_mesh(&mesh)` without vector cloning.

[`execution.rs`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/execution.rs)
- Updated
  [`commands.push(DrawCommand::Mesh(mesh))`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/execution.rs#L135)
  and
  [`tessellate_command`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/execution.rs#L337).
- Updated
  [`test_forward_left_forward`](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/execution.rs#L487)
  to instantiate `mut state` directly without dummy cloning.

---

```bash
cargo test --lib
```
Output:
```
running 10 tests
test circle_geometry::tests::test_circle_left_geometry ... ok
test circle_geometry::tests::test_circle_right_geometry ... ok
test general::angle::tests::degrees_to_radians_roundtrip ... ok
test general::angle::tests::from_integer ... ok
test execution::tests::test_forward_left_forward ... ok
test general::angle::tests::negation ... ok
test tweening::tests::test_animated_mode_pops_to_current_tween ... ok
test tweening::tests::test_instant_mode_drains_queue ... ok
test
tweening::tests::test_instant_mode_respects_batch_limit_and_retains_pending
... ok
test tweening::tests::test_streaming_append_commands_does_not_accumulate
... ok

test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.00s
```

All targets verified with `cargo check --all-targets` and `cargo clippy
--lib`.
This commit is contained in:
2026-09-18 21:34:07 +02:00
parent 1d0e149d22
commit ea9a8839c4
4 changed files with 33 additions and 68 deletions
+10 -10
View File
@@ -33,8 +33,8 @@ pub(crate) fn render_world_with_tweens(world: &TurtleWorld, zoom_level: f32) {
for turtle in &world.turtles {
for cmd in &turtle.commands {
match cmd {
DrawCommand::Mesh { data } => {
draw_mesh(&data.to_mesh());
DrawCommand::Mesh(mesh) => {
draw_mesh(mesh);
}
DrawCommand::Text {
text,
@@ -224,8 +224,8 @@ pub(crate) fn render_world_with_tweens(world: &TurtleWorld, zoom_level: f32) {
&all_contours,
fill_state.fill_color,
) {
Ok(mesh_data) => {
draw_mesh(&mesh_data.to_mesh());
Ok(mesh) => {
draw_mesh(&mesh);
}
Err(e) => {
tracing::error!("Failed to tessellate fill preview: {:?}", e);
@@ -304,9 +304,9 @@ fn draw_tween_arc(
);
// Draw center using Lyon tessellation this helps visualizing what is done.
if let Ok(mesh_data) = crate::tessellation::tessellate_circle(geom.center, 5.0, GRAY, true, 1.0)
if let Ok(mesh) = crate::tessellation::tessellate_circle(geom.center, 5.0, GRAY, true, 1.0)
{
draw_mesh(&mesh_data.to_mesh());
draw_mesh(&mesh);
}
// Calculate how much of the arc we've traveled based on tween progress
@@ -316,7 +316,7 @@ fn draw_tween_arc(
let progress = CubicInOut.tween(1.0, t as f32); // tween from 0 to 1
// Use Lyon to tessellate and draw the partial arc
if let Ok(mesh_data) = crate::tessellation::tessellate_arc(
if let Ok(mesh) = crate::tessellation::tessellate_arc(
geom.center,
radius,
geom.start_angle_from_center.to_degrees(),
@@ -326,7 +326,7 @@ fn draw_tween_arc(
((steps as f32 * progress).ceil() as usize).max(1),
direction,
) {
draw_mesh(&mesh_data.to_mesh());
draw_mesh(&mesh);
}
}
@@ -343,10 +343,10 @@ pub(crate) fn draw_turtle(turtle_params: &TurtleParams) {
.collect();
// Use Lyon for turtle shape too
if let Ok(mesh_data) =
if let Ok(mesh) =
tessellation::tessellate_polygon(&absolute_vertices, Color::new(0.0, 0.5, 1.0, 1.0))
{
draw_mesh(&mesh_data.to_mesh());
draw_mesh(&mesh);
} else {
// Fallback to simple triangle fan if Lyon fails
let first = absolute_vertices[0];