Skip to content

Conversation

tigregalis
Copy link
Contributor

@tigregalis tigregalis commented Oct 9, 2025

Objective

I was trying to implement gizmos for Rings - but ran into the fact that builders (GizmoPrimitive2d::Output) contain a mutable borrow of the gizmos, so I couldn't construct "composites" of primitives that were also configurable i.e. I need to be able to generate two sub-gizmos from a single composite primitive:

struct CompositePrimitive {
   primitive_1: Primitive1,
   primitive_2: Primitive2,
}

// GizmoPrimitive2d<Primitive1>::Output = Primitive1ConfigurableBuilder
// GizmoPrimitive2d<Primitive2>::Output = Primitive2ConfigurableBuilder
// GizmoPrimitive2d<CompositePrimitive>::Output = CompositePrimitiveBuilde

struct CompositePrimitiveBuilder {
   primitive_1_builder: Primitive1ConfigurableBuilder, // first mutable borrow of GizmoBuffer
   primitive_2_builder: Primitive2ConfigurableBuilder, // no good due to second mutable borrow of GizmoBuffer
}

Solution

The solution is to remove the borrow from each of the builders and defer the use of the GizmoBuffer (i.e. the builder is now just data).

Essentially this mirrors the Meshable implementation:

  1. A trait ToGizmoBlueprint2d to take a primitive and generate a blueprint / descriptor / builder from it (where the data to actually generate the gizmo is sourced from) primitive -> builder (data only, no borrow)
  2. A trait GizmoBlueprint that uses the data in the blueprint to generate a gizmo builder -> gizmo (GizmoBuffer borrowed on demand)
  3. (Not in Meshable) A "Drop guard" struct GizmoBuilder2d that is a wrapper around the builder and the GizmoBuffer-borrow, and provides easy access to the inner builders (via Deref/DerefMut)

All the gizmo drawing instructions that were previously placed inside the Drop implementation for every builder or directly inside the GizmoPrimitive2d::primitive_2d implementation now lives in the Blueprint2d::build_2d implementation. Now there is only a single Drop implementation, that is not user-facing, in GizmoBuilder2d ; another convenient benefit is the gizmos.enabled check can be done in this one place, and it won't require users to implement it themselves.

GizmoBuilder2d could probably also be simplified, so it always returns GizmoBuilder2d<'a, Self::Output<'a>, Config, Clear> or even turned into a blanket implementation.

Generic composites, non-generic composites and custom primitives should be fairly trivial with this design.

I'm far from done with this but I'm putting this out just as a draft to see whether this is the right path forward or not before I continue working on it.

One thing that may need to be resolved is that the builder/blueprint is owned, so something like a Polyline has to be cloned at some point. I also suspect it's possible to have a single pair of the Blueprint traits (ToGizmoBlueprint and GizmoBlueprint) instead of dividing them into separate 2d and 3d traits (not that I've implemented the 3d one yet).

I hadn't seen #20049 until after I got this far. It seems like it goes down a somewhat similar path in some ways, at least as far as deferring the use of the GizmoBuffer goes, but I'm not sure whether it solves the builder-reuse problem.

Testing

So far, the 2d_gizmos examples continues to work the same way it used to, as far as I'm aware.

@tigregalis tigregalis force-pushed the primitive-gizmos-rework branch from e9aeffc to 3fa6fb2 Compare October 9, 2025 16:17
@tigregalis
Copy link
Contributor Author

@lynn-lumen do you mind having a look at this?

@alice-i-cecile alice-i-cecile added C-Code-Quality A section of code that is hard to understand or change C-Usability A targeted quality-of-life change that makes Bevy easier to use A-Gizmos Visual editor and debug gizmos X-Contentious There are nontrivial implications that should be thought through S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Oct 9, 2025
@lynn-lumen
Copy link
Contributor

There is also #20246. All of these PRs take a similar approach, and I agree with the direction. However, using Gizmos as structs should probably be a separate PR if that approach is blessed. I think @alice-i-cecile 's input on this may be valuable.

@tigregalis tigregalis force-pushed the primitive-gizmos-rework branch from 3fa6fb2 to f3a9af4 Compare October 11, 2025 03:50
@tigregalis tigregalis force-pushed the primitive-gizmos-rework branch from 66d3cc4 to 06beda3 Compare October 11, 2025 04:45
@tigregalis
Copy link
Contributor Author

There is also #20246. All of these PRs take a similar approach, and I agree with the direction. However, using Gizmos as structs should probably be a separate PR if that approach is blessed. I think @alice-i-cecile 's input on this may be valuable.

Regarding #20246, so it turns out that with a simple blanket From implementation...

impl<B: GizmoBlueprint2d> From<B> for GizmoAsset {
    fn from(mut value: B) -> Self {
        let mut asset = GizmoAsset::new();
        value.build_2d(&mut asset);
        asset
    }
}

... we already get something very close to what Meshable provides...

fn setup(
    mut commands: Commands,
    mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) {
    let mut gizmo = GizmoAsset::new();

    commands.spawn((
        Gizmo {
            handle: gizmo_assets.add(Circle::new(0.5).to_blueprint_2d(Vec2::ZERO, PURPLE).resolution(20)),
            line_config: GizmoLineConfig {
                width: 5.,
                ..default()
            },
            ..default()
        },
        Transform::from_xyz(4., 1., 0.),
    ));
}

and most of the way towards the design provided in that issue. The only real ergonomic pain is all the fields in Gizmo.

Assuming we moved forward with my PR (bikeshedding on naming welcome), the next PR would be to decompose Gizmo into smaller components with required components so that it mirrors Mesh2d, i.e. something like

#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default, Clone, PartialEq)]
#[require(GizmoLineConfig, GizmoDepthBias, Transform)]
pub struct Gizmo2d(pub Handle<GizmoAsset>);

fn setup(
    mut commands: Commands,
    mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
) {
    commands.spawn((
        Gizmo2d(gizmo_assets.add(Circle::new(0.5).to_blueprint_2d(Vec2::ZERO, PURPLE).resolution(20))),
        Transform::from_xyz(4., 1., 0.),
    ));
}

After that, BSN might bring us closer to literally DrawGizmo(impl into Gizmo)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Gizmos Visual editor and debug gizmos C-Code-Quality A section of code that is hard to understand or change C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants