• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Xevion / Pac-Man / 17447763477

03 Sep 2025 10:31PM UTC coverage: 38.371% (-3.2%) from 41.545%
17447763477

push

github

Xevion
feat: implement optimized text rendering by caching font characters into special atlas

0 of 198 new or added lines in 3 files covered. (0.0%)

6 existing lines in 2 files now uncovered.

1135 of 2958 relevant lines covered (38.37%)

918.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/systems/debug.rs
1
//! Debug rendering system
2
use std::cmp::Ordering;
3

4
use crate::constants::BOARD_PIXEL_OFFSET;
5
use crate::map::builder::Map;
6
use crate::systems::{Collider, CursorPosition, NodeId, Position, SystemTimings};
7
use crate::texture::ttf::{TtfAtlas, TtfRenderer};
8
use bevy_ecs::resource::Resource;
9
use bevy_ecs::system::{NonSendMut, Query, Res};
10
use glam::{IVec2, UVec2, Vec2};
11
use sdl2::pixels::Color;
12
use sdl2::rect::{Point, Rect};
13
use sdl2::render::{Canvas, Texture};
14
use sdl2::video::Window;
15

16
#[derive(Resource, Default, Debug, Copy, Clone)]
17
pub struct DebugState {
18
    pub enabled: bool,
19
}
20

21
fn f32_to_u8(value: f32) -> u8 {
×
22
    (value * 255.0) as u8
×
23
}
×
24

25
/// Resource to hold the debug texture for persistent rendering
26
pub struct DebugTextureResource(pub Texture);
27

28
/// Resource to hold the TTF text atlas
29
pub struct TtfAtlasResource(pub TtfAtlas);
30

31
/// Transforms a position from logical canvas coordinates to output canvas coordinates (with board offset)
32
fn transform_position_with_offset(pos: Vec2, scale: f32) -> IVec2 {
×
33
    ((pos + BOARD_PIXEL_OFFSET.as_vec2()) * scale).as_ivec2()
×
34
}
×
35

36
/// Renders timing information in the top-left corner of the screen using the debug text atlas
37
fn render_timing_display(
×
38
    canvas: &mut Canvas<Window>,
×
39
    timings: &SystemTimings,
×
NEW
40
    text_renderer: &TtfRenderer,
×
NEW
41
    atlas: &mut TtfAtlas,
×
42
) {
×
43
    // Format timing information using the formatting module
×
44
    let lines = timings.format_timing_display();
×
NEW
45
    let line_height = text_renderer.text_height(atlas) as i32 + 2; // Add 2px line spacing
×
46
    let padding = 10;
×
47

×
48
    // Calculate background dimensions
×
49
    let max_width = lines
×
50
        .iter()
×
51
        .filter(|l| !l.is_empty()) // Don't consider empty lines for width
×
NEW
52
        .map(|line| text_renderer.text_width(atlas, line))
×
53
        .max()
×
54
        .unwrap_or(0);
×
55

×
56
    // Only draw background if there is text to display
×
57
    let total_height = (lines.len() as u32) * line_height as u32;
×
58
    if max_width > 0 && total_height > 0 {
×
59
        let bg_padding = 5;
×
60

×
61
        // Draw background
×
62
        let bg_rect = Rect::new(
×
63
            padding - bg_padding,
×
64
            padding - bg_padding,
×
65
            max_width + (bg_padding * 2) as u32,
×
66
            total_height + bg_padding as u32,
×
67
        );
×
68
        canvas.set_blend_mode(sdl2::render::BlendMode::Blend);
×
69
        canvas.set_draw_color(Color::RGBA(40, 40, 40, 180));
×
70
        canvas.fill_rect(bg_rect).unwrap();
×
71
    }
×
72

73
    for (i, line) in lines.iter().enumerate() {
×
74
        if line.is_empty() {
×
75
            continue;
×
76
        }
×
77

×
78
        // Position each line below the previous one
×
NEW
79
        let y_pos = padding + (i as i32 * line_height);
×
NEW
80
        let position = Vec2::new(padding as f32, y_pos as f32);
×
NEW
81

×
NEW
82
        // Render the line using the debug text renderer
×
NEW
83
        text_renderer
×
NEW
84
            .render_text(canvas, atlas, line, position, Color::RGBA(255, 255, 255, 200))
×
NEW
85
            .unwrap();
×
86
    }
87
}
×
88

89
#[allow(clippy::too_many_arguments)]
90
pub fn debug_render_system(
×
91
    mut canvas: NonSendMut<&mut Canvas<Window>>,
×
92
    mut debug_texture: NonSendMut<DebugTextureResource>,
×
NEW
93
    mut ttf_atlas: NonSendMut<TtfAtlasResource>,
×
94
    debug_state: Res<DebugState>,
×
95
    timings: Res<SystemTimings>,
×
96
    map: Res<Map>,
×
97
    colliders: Query<(&Collider, &Position)>,
×
98
    cursor: Res<CursorPosition>,
×
99
) {
×
100
    if !debug_state.enabled {
×
101
        return;
×
102
    }
×
103
    let scale =
×
104
        (UVec2::from(canvas.output_size().unwrap()).as_vec2() / UVec2::from(canvas.logical_size()).as_vec2()).min_element();
×
105

×
NEW
106
    // Create debug text renderer
×
NEW
107
    let text_renderer = TtfRenderer::new(1.0);
×
108

109
    let cursor_world_pos = match *cursor {
×
110
        CursorPosition::None => None,
×
111
        CursorPosition::Some { position, .. } => Some(position - BOARD_PIXEL_OFFSET.as_vec2()),
×
112
    };
113

114
    // Draw debug info on the high-resolution debug texture
115
    canvas
×
116
        .with_texture_canvas(&mut debug_texture.0, |debug_canvas| {
×
117
            // Clear the debug canvas
×
118
            debug_canvas.set_draw_color(Color::RGBA(0, 0, 0, 0));
×
119
            debug_canvas.clear();
×
120

121
            // Find the closest node to the cursor
122
            let closest_node = if let Some(cursor_world_pos) = cursor_world_pos {
×
123
                map.graph
×
124
                    .nodes()
×
125
                    .map(|node| node.position.distance(cursor_world_pos))
×
126
                    .enumerate()
×
127
                    .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Less))
×
128
                    .map(|(id, _)| id)
×
129
            } else {
130
                None
×
131
            };
132

133
            debug_canvas.set_draw_color(Color::GREEN);
×
134
            for (collider, position) in colliders.iter() {
×
135
                let pos = position.get_pixel_position(&map.graph).unwrap();
×
136

×
137
                // Transform position and size using common methods
×
138
                let pos = (pos * scale).as_ivec2();
×
139
                let size = (collider.size * scale) as u32;
×
140

×
141
                let rect = Rect::from_center(Point::from((pos.x, pos.y)), size, size);
×
142
                debug_canvas.draw_rect(rect).unwrap();
×
143
            }
×
144

145
            debug_canvas.set_draw_color(Color {
×
146
                a: f32_to_u8(0.4),
×
147
                ..Color::RED
×
148
            });
×
149
            debug_canvas.set_blend_mode(sdl2::render::BlendMode::Blend);
×
150
            for (start_node, end_node) in map.graph.edges() {
×
151
                let start_node_model = map.graph.get_node(start_node).unwrap();
×
152
                let end_node = map.graph.get_node(end_node.target).unwrap().position;
×
153

×
154
                // Transform positions using common method
×
155
                let start = transform_position_with_offset(start_node_model.position, scale);
×
156
                let end = transform_position_with_offset(end_node, scale);
×
157

×
158
                debug_canvas
×
159
                    .draw_line(Point::from((start.x, start.y)), Point::from((end.x, end.y)))
×
160
                    .unwrap();
×
161
            }
×
162

163
            for (id, node) in map.graph.nodes().enumerate() {
×
164
                let pos = node.position;
×
165

×
166
                // Set color based on whether the node is the closest to the cursor
×
167
                debug_canvas.set_draw_color(Color {
×
168
                    a: f32_to_u8(if Some(id) == closest_node { 0.75 } else { 0.6 }),
×
169
                    ..(if Some(id) == closest_node {
×
170
                        Color::YELLOW
×
171
                    } else {
172
                        Color::BLUE
×
173
                    })
174
                });
175

176
                // Transform position using common method
177
                let pos = transform_position_with_offset(pos, scale);
×
178
                let size = (2.0 * scale) as u32;
×
179

×
180
                debug_canvas
×
181
                    .fill_rect(Rect::new(pos.x - (size as i32 / 2), pos.y - (size as i32 / 2), size, size))
×
182
                    .unwrap();
×
183
            }
184

185
            // Render node ID if a node is highlighted
186
            if let Some(closest_node_id) = closest_node {
×
187
                let node = map.graph.get_node(closest_node_id as NodeId).unwrap();
×
188
                let pos = transform_position_with_offset(node.position, scale);
×
189

×
NEW
190
                let node_id_text = closest_node_id.to_string();
×
NEW
191
                let text_pos = Vec2::new((pos.x + 10) as f32, (pos.y - 5) as f32);
×
NEW
192

×
NEW
193
                text_renderer
×
NEW
194
                    .render_text(
×
NEW
195
                        debug_canvas,
×
NEW
196
                        &mut ttf_atlas.0,
×
NEW
197
                        &node_id_text,
×
NEW
198
                        text_pos,
×
NEW
199
                        Color {
×
NEW
200
                            a: f32_to_u8(0.4),
×
NEW
201
                            ..Color::WHITE
×
NEW
202
                        },
×
NEW
203
                    )
×
204
                    .unwrap();
×
205
            }
×
206

207
            // Render timing information in the top-left corner
NEW
208
            render_timing_display(debug_canvas, &timings, &text_renderer, &mut ttf_atlas.0);
×
209
        })
×
210
        .unwrap();
×
211
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc