Skip to content

Commit 1040ed5

Browse files
committed
Refactor and document
Various name changes and extra doc string. Also fixes a likely issue with `mark_expired`, which would add duplicates to the `expired` stack.
1 parent 7bf00c8 commit 1040ed5

File tree

4 files changed

+95
-49
lines changed

4 files changed

+95
-49
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ This is technically a non-breaking change (i.e. your code will still compile) be
8686
* `DebugLines` is now a `SystemParam`, you should replace `mut lines: ResMut<DebugLines>`
8787
in your code by a simple `mut lines: DebugLines`
8888
* The depth check is not supported through the `DebugLines.depth_check` field
89-
anymore. You should set it when initializing the plugin with
90-
`.add_plugin(DebugLinesPlugin::draw_on_top(false)`
91-
* `DebugLinesPlugin` has now a constructor, you should replace `.add_plugin(DebugLinesPlugin)`
89+
anymore. You need to set it when initializing the plugin. By default depth
90+
test is disabled but can be enabled with:
91+
`.add_plugin(DebugLinesPlugin::always_in_front())`
92+
* `DebugLinesPlugin` now has a constructor, you should replace `.add_plugin(DebugLinesPlugin)`
9293
in your code by `.add_plugin(DebugLinesPlugin::default())`.
9394

9495
## Bevy Version Support

examples/3d.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ fn setup(mut commands: Commands, mut lines: DebugLines) {
1717
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 5.0)),
1818
..Default::default()
1919
});
20-
// Demonstrate "retained mod" lines
20+
// A line that stays on screen 9 seconds
2121
lines.line_gradient(
2222
Vec3::new(1.0, -1.0, -1.0),
2323
Vec3::new(-1.0, 1.0, 1.0),
24-
100.0,
24+
9.0,
2525
Color::CYAN,
2626
Color::MIDNIGHT_BLUE,
2727
);

examples/depth_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ fn main() {
66
App::new()
77
.insert_resource(Msaa { samples: 4 })
88
.add_plugins(DefaultPlugins)
9-
.add_plugin(DebugLinesPlugin::draw_on_top(true))
9+
.add_plugin(DebugLinesPlugin::always_in_front())
1010
.add_startup_system(setup.system())
1111
.add_system(demo.system())
1212
.run();

src/lib.rs

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,36 @@ const DEBUG_LINES_SHADER_HANDLE: HandleUntyped =
3636
///
3737
/// App::new()
3838
/// .add_plugins(DefaultPlugins)
39-
/// .add_plugin(DebugLinesPlugin)
39+
/// .add_plugin(DebugLinesPlugin::default())
40+
/// .run();
41+
/// ```
42+
///
43+
/// Alternatively, you can initialize the plugin without depth testing, so that
44+
/// debug lines are always visible, even when behind other objects. For this,
45+
/// you need to use the [`DebugLinesPlugin::always_in_front`] constructor.
46+
/// ```
47+
/// use bevy::prelude::*;
48+
/// use bevy_prototype_debug_lines::*;
49+
///
50+
/// App::new()
51+
/// .add_plugins(DefaultPlugins)
52+
/// .add_plugin(DebugLinesPlugin::always_in_front())
4053
/// .run();
4154
/// ```
4255
#[derive(Debug, Default, Clone)]
4356
pub struct DebugLinesPlugin {
44-
always_on_top: bool,
57+
always_in_front: bool,
4558
}
4659
impl DebugLinesPlugin {
47-
pub fn draw_on_top(always_on_top: bool) -> Self {
48-
DebugLinesPlugin { always_on_top }
60+
/// Always show debug lines in front of other objects
61+
///
62+
/// This disables depth culling for the debug line, so that they
63+
/// are always visible, regardless of whether there are other objects in
64+
/// front.
65+
pub fn always_in_front() -> Self {
66+
DebugLinesPlugin {
67+
always_in_front: true,
68+
}
4969
}
5070
}
5171
impl Plugin for DebugLinesPlugin {
@@ -55,8 +75,8 @@ impl Plugin for DebugLinesPlugin {
5575
DEBUG_LINES_SHADER_HANDLE,
5676
Shader::from_wgsl(include_str!("debuglines.wgsl")),
5777
);
58-
app.init_resource::<ImmLinesStorage>();
59-
app.init_resource::<RetLinesStorage>();
78+
app.init_resource::<ImmediateLinesStorage>();
79+
app.init_resource::<RetainedLinesStorage>();
6080
app.add_startup_system(setup_system)
6181
.add_system_to_stage(CoreStage::Last, update_debug_lines_mesh.label("draw_lines"));
6282
app.sub_app_mut(RenderApp)
@@ -126,8 +146,7 @@ fn update_debug_lines_mesh(
126146
Retained(i) => lines.retained.fill_attributes(time, mesh, i),
127147
}
128148
}
129-
// This needs to be done after `fill_attributes` because of the immediate buffer clears
130-
lines.mark_expired();
149+
lines.frame_init();
131150
}
132151

133152
/// Initialize [`DebugLinesMesh`]'s [`Mesh`].
@@ -157,7 +176,8 @@ fn extract_debug_lines(mut commands: Commands, query: Query<Entity, With<DebugLi
157176
/// Marker Component for the [`Entity`] associated with the meshes rendered with the
158177
/// debuglines.wgsl shader.
159178
///
160-
/// Stores the index of the mesh for the logic of `ImmLinesStorage` and `RetLinesStorage`
179+
/// Stores the index of the mesh for the logic of [`ImmediateLinesStorage`] and
180+
/// [`RetainedLinesStorage`]
161181
#[derive(Component)]
162182
enum DebugLinesMesh {
163183
/// Meshes for duration=0.0 lines
@@ -190,11 +210,11 @@ struct RenderDebugLinesMesh;
190210
/// user **should absolutely not interact with this**.
191211
#[derive(Debug, Default)]
192212
#[doc(hidden)]
193-
pub struct ImmLinesStorage {
213+
pub struct ImmediateLinesStorage {
194214
positions: Vec<[f32; 3]>,
195215
colors: Vec<[f32; 4]>,
196216
}
197-
impl ImmLinesStorage {
217+
impl ImmediateLinesStorage {
198218
fn add_at(&mut self, line_index: usize, position: Line<Vec3>, color: Line<Color>) {
199219
let i = line_index * 2;
200220
self.colors[i] = color.start.into();
@@ -216,7 +236,11 @@ impl ImmLinesStorage {
216236
}
217237
}
218238

219-
fn mark_expired(&mut self) {
239+
/// Cull all lines that shouldn't be rendered anymore
240+
///
241+
/// Since all lines in `ImmediateLinesStorage` should be removed each frame, this
242+
/// simply set the length of the positions and colors vectors to 0.
243+
fn frame_init(&mut self) {
220244
self.positions.clear();
221245
self.colors.clear();
222246
}
@@ -235,6 +259,7 @@ impl ImmLinesStorage {
235259
}
236260
}
237261

262+
/// Copy line descriptions into mesh attribute buffers
238263
fn fill_attributes(&self, mesh: &mut Mesh, mesh_index: usize) {
239264
use VertexAttributeValues::{Float32x3, Float32x4};
240265
if let Some(Float32x3(vbuffer)) = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) {
@@ -256,65 +281,81 @@ impl ImmLinesStorage {
256281
/// The [`DebugLines`] storage.
257282
#[derive(Debug, Default)]
258283
#[doc(hidden)]
259-
pub struct RetLinesStorage {
260-
inner: ImmLinesStorage,
261-
/// The timestamp in seconds after which a line should not be rendered anymore.
284+
pub struct RetainedLinesStorage {
285+
lines: ImmediateLinesStorage,
286+
/// The timestamp after which a line should not be rendered anymore.
262287
///
263-
/// `timestamp[i]` corresponds to `inner.add_at(i)`
264-
timestamp: Vec<f32>,
288+
/// It is represented as the number of seconds since the game started.
289+
/// `expire_time[i]` corresponds to the i-th line in `lines` buffer.
290+
expire_time: Vec<f32>,
265291
/// Index of lines that can be safely overwritten
266292
expired: Vec<u32>,
293+
/// Whether we have computed expired lines this frame
294+
expired_marked: bool,
267295
}
268-
impl RetLinesStorage {
269-
fn add_line(&mut self, position: Line<Vec3>, color: Line<Color>, timestamp: f32) {
296+
impl RetainedLinesStorage {
297+
fn add_line(&mut self, position: Line<Vec3>, color: Line<Color>, time: f32, duration: f32) {
298+
if !self.expired_marked {
299+
self.expired_marked = true;
300+
self.mark_expired(time);
301+
}
302+
let expire_time = time + duration;
270303
if let Some(replaceable) = self.expired.pop() {
271304
let i = replaceable as usize;
272-
self.inner.add_at(i, position, color);
273-
self.timestamp[i] = timestamp;
274-
} else if self.timestamp.len() >= MAX_LINES {
305+
self.lines.add_at(i, position, color);
306+
self.expire_time[i] = expire_time;
307+
} else if self.expire_time.len() >= MAX_LINES {
275308
let i = MAX_LINES - 1;
276-
self.inner.add_at(i, position, color);
277-
self.timestamp[i] = timestamp;
309+
self.lines.add_at(i, position, color);
310+
self.expire_time[i] = expire_time;
278311
} else {
279-
self.inner.push(position, color);
280-
self.timestamp.push(timestamp);
312+
self.lines.push(position, color);
313+
self.expire_time.push(expire_time);
281314
}
282315
}
283316

317+
/// Fill the mesh indice buffer
318+
///
319+
/// We only add the indices of points for the non-expired lines.
284320
fn fill_indices(&self, time: f32, buffer: &mut Vec<u16>, mesh: usize) {
285321
buffer.clear();
286-
if let Some(new_content) = self.timestamp.chunks(MAX_LINES_PER_MESH).nth(mesh) {
322+
if let Some(new_content) = self.expire_time.chunks(MAX_LINES_PER_MESH).nth(mesh) {
287323
buffer.extend(
288324
new_content
289325
.iter()
290326
.enumerate()
291-
.filter(|(_, e)| **e >= time)
327+
.filter(|(_, expires_at)| **expires_at >= time)
292328
.map(|(i, _)| i as u16)
293329
.flat_map(|i| [i * 2, i * 2 + 1]),
294330
);
295331
}
296332
}
297333

298334
fn mark_expired(&mut self, time: f32) {
335+
self.expired.clear();
299336
self.expired.extend(
300-
self.timestamp
337+
self.expire_time
301338
.iter()
302339
.enumerate()
303-
.filter(|(i, e)| **e < time && i % 2 == 0)
340+
.filter(|(i, expires_at)| **expires_at < time && i % 2 == 0)
304341
.map(|(i, _)| i as u32 / 2),
305342
);
306343
}
307344

345+
fn frame_init(&mut self) {
346+
self.expired_marked = false;
347+
}
348+
308349
fn fill_attributes(&self, time: f32, mesh: &mut Mesh, mesh_index: usize) {
309350
use VertexAttributeValues::{Float32x3, Float32x4};
310351
if let Some(Indices::U16(indices)) = mesh.indices_mut() {
311352
self.fill_indices(time, indices, mesh_index);
312353
}
313354
if let Some(Float32x3(vbuffer)) = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) {
314-
self.inner.fill_vertexes(vbuffer, mesh_index);
355+
self.lines.fill_vertexes(vbuffer, mesh_index);
315356
}
316357
if let Some(Float32x4(cbuffer)) = mesh.attribute_mut(Mesh::ATTRIBUTE_COLOR) {
317-
self.inner.fill_colors(cbuffer, mesh_index);
358+
self.lines.fill_colors(cbuffer, mesh_index);
318359
}
319360
}
320361
}
@@ -345,8 +386,8 @@ impl RetLinesStorage {
345386
/// ```
346387
#[derive(SystemParam)]
347388
pub struct DebugLines<'w, 's> {
348-
immediate: ResMut<'w, ImmLinesStorage>,
349-
retained: ResMut<'w, RetLinesStorage>,
389+
immediate: ResMut<'w, ImmediateLinesStorage>,
390+
retained: ResMut<'w, RetainedLinesStorage>,
350391
time: Res<'w, Time>,
351392
#[system_param(ignore)]
352393
_lifetimes: PhantomData<&'s ()>,
@@ -401,30 +442,34 @@ impl<'w, 's> DebugLines<'w, 's> {
401442
if duration == 0.0 {
402443
self.immediate.add_line(positions, colors);
403444
} else {
404-
let timestamp = self.time.time_since_startup().as_secs_f32() + duration;
405-
self.retained.add_line(positions, colors, timestamp);
445+
let time = self.time.time_since_startup().as_secs_f32();
446+
self.retained.add_line(positions, colors, time, duration);
406447
}
407448
}
408449

409-
fn mark_expired(&mut self) {
410-
let time = self.time.time_since_startup().as_secs_f32();
411-
self.immediate.mark_expired();
412-
self.retained.mark_expired(time);
450+
/// Prepare [`ImmediateLinesStorage`] and [`RetainedLinesStorage`] for next
451+
/// frame.
452+
///
453+
/// This clears the immediate mod buffers and tells the retained mod
454+
/// buffers to recompute expired lines list.
455+
fn frame_init(&mut self) {
456+
self.immediate.frame_init();
457+
self.retained.frame_init();
413458
}
414459
}
415460

416461
struct DebugLinePipeline {
417462
mesh_pipeline: MeshPipeline,
418463
shader: Handle<Shader>,
419-
always_on_top: bool,
464+
always_in_front: bool,
420465
}
421466
impl FromWorld for DebugLinePipeline {
422467
fn from_world(render_world: &mut World) -> Self {
423468
let dbl_plugin = render_world.get_resource::<DebugLinesPlugin>().unwrap();
424469
DebugLinePipeline {
425470
mesh_pipeline: render_world.get_resource::<MeshPipeline>().unwrap().clone(),
426471
shader: DEBUG_LINES_SHADER_HANDLE.typed(),
427-
always_on_top: dbl_plugin.always_on_top,
472+
always_in_front: dbl_plugin.always_in_front,
428473
}
429474
}
430475
}
@@ -459,7 +504,7 @@ impl SpecializedPipeline for DebugLinePipeline {
459504
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();
460505
descriptor.primitive.topology = PrimitiveTopology::LineList;
461506
descriptor.primitive.cull_mode = None;
462-
let depth_rate = if self.always_on_top {
507+
let depth_rate = if self.always_in_front {
463508
f32::INFINITY
464509
} else {
465510
1.0

0 commit comments

Comments
 (0)