Skip to content

Commit 76744bf

Browse files
Mark ghost nodes as experimental and partially feature flag them (#15961)
# Objective As discussed in #15341, ghost nodes are a contentious and experimental feature. In the interest of enabling ecosystem experimentation, we've decided to keep them in Bevy 0.15. That said, we don't use them internally, and don't expect third-party crates to support them. If the experimentation returns a negative result (they aren't very useful, an alternative design is preferred etc) they will be removed. We should clearly communicate this status to users, and make sure that users don't use ghost nodes in their projects without a very clear understanding of what they're getting themselves into. ## Solution To make life easy for users (and Bevy), `GhostNode` and all associated helpers remain public and are always available. However, actually constructing these requires enabling a feature flag that's clearly marked as experimental. To do so, I've added a meaningless private field. When the feature flag is enabled, our constructs (`new` and `default`) can be used. I've added a `new` constructor, which should be preferred over `Default::default` as that can be readily deprecated, allowing us to prompt users to swap over to the much nicer `GhostNode` syntax once this is a unit struct again. Full credit: this was mostly @cart's design: I'm just implementing it! ## Testing I've run the ghost_nodes example and it fails to compile without the feature flag. With the feature flag, it works fine :) --------- Co-authored-by: Zachary Harrold <[email protected]>
1 parent f4d9c52 commit 76744bf

File tree

12 files changed

+102
-32
lines changed

12 files changed

+102
-32
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ reflect_functions = ["bevy_internal/reflect_functions"]
444444
# Enable winit custom cursor support
445445
custom_cursor = ["bevy_internal/custom_cursor"]
446446

447+
# Experimental support for nodes that are ignored for UI layouting
448+
ghost_nodes = ["bevy_internal/ghost_nodes"]
449+
447450
[dependencies]
448451
bevy_internal = { path = "crates/bevy_internal", version = "0.15.0-dev", default-features = false }
449452

@@ -3081,6 +3084,7 @@ wasm = true
30813084
name = "ghost_nodes"
30823085
path = "examples/ui/ghost_nodes.rs"
30833086
doc-scrape-examples = true
3087+
required-features = ["ghost_nodes"]
30843088

30853089
[package.metadata.example.ghost_nodes]
30863090
name = "Ghost Nodes"

crates/bevy_internal/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ reflect_functions = [
241241
# Enable winit custom cursor support
242242
custom_cursor = ["bevy_winit/custom_cursor"]
243243

244+
# Experimental support for nodes that are ignored for UI layouting
245+
ghost_nodes = ["bevy_ui/ghost_nodes"]
246+
244247
[dependencies]
245248
# bevy
246249
bevy_a11y = { path = "../bevy_a11y", version = "0.15.0-dev" }

crates/bevy_ui/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ default = ["bevy_ui_picking_backend"]
4949
serialize = ["serde", "smallvec/serde", "bevy_math/serialize"]
5050
bevy_ui_picking_backend = ["bevy_picking"]
5151

52+
# Experimental features
53+
ghost_nodes = []
54+
5255
[lints]
5356
workspace = true
5457

crates/bevy_ui/src/accessibility.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::{
2+
experimental::UiChildren,
23
prelude::{Button, Label},
34
widget::TextUiReader,
4-
Node, UiChildren, UiImage,
5+
Node, UiImage,
56
};
67
use bevy_a11y::{
78
accesskit::{NodeBuilder, Rect, Role},

crates/bevy_ui/src/ghost_hierarchy.rs renamed to crates/bevy_ui/src/experimental/ghost_hierarchy.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use bevy_hierarchy::{Children, HierarchyQueryExt, Parent};
55
use bevy_reflect::prelude::*;
66
use bevy_render::view::Visibility;
77
use bevy_transform::prelude::Transform;
8+
use core::marker::PhantomData;
89
use smallvec::SmallVec;
910

1011
use crate::Node;
@@ -14,10 +15,30 @@ use crate::Node;
1415
/// The UI systems will traverse past these and treat their first non-ghost descendants as direct children of their first non-ghost ancestor.
1516
///
1617
/// Any components necessary for transform and visibility propagation will be added automatically.
17-
#[derive(Component, Default, Debug, Copy, Clone, Reflect)]
18+
///
19+
/// Instances of this type cannot be constructed unless the `ghost_nodes` feature is enabled.
20+
#[derive(Component, Debug, Copy, Clone, Reflect)]
21+
#[cfg_attr(feature = "ghost_nodes", derive(Default))]
1822
#[reflect(Component, Debug)]
1923
#[require(Visibility, Transform)]
20-
pub struct GhostNode;
24+
pub struct GhostNode {
25+
// This is a workaround to ensure that GhostNode is only constructable when the appropriate feature flag is enabled
26+
#[reflect(ignore)]
27+
unconstructable: PhantomData<()>, // Spooky!
28+
}
29+
30+
#[cfg(feature = "ghost_nodes")]
31+
impl GhostNode {
32+
/// Creates a new ghost node.
33+
///
34+
/// This method is only available when the `ghost_node` feature is enabled,
35+
/// and will eventually be deprecated then removed in favor of simply using `GhostNode` as no meaningful data is stored.
36+
pub const fn new() -> Self {
37+
GhostNode {
38+
unconstructable: PhantomData,
39+
}
40+
}
41+
}
2142

2243
/// System param that allows iteration of all UI root nodes.
2344
///
@@ -140,7 +161,7 @@ impl<'w, 's> Iterator for UiChildrenIter<'w, 's> {
140161
}
141162
}
142163

143-
#[cfg(test)]
164+
#[cfg(all(test, feature = "ghost_nodes"))]
144165
mod tests {
145166
use bevy_ecs::{
146167
prelude::Component,
@@ -165,18 +186,20 @@ mod tests {
165186
.with_children(|parent| {
166187
parent.spawn((A(2), NodeBundle::default()));
167188
parent
168-
.spawn((A(3), GhostNode))
189+
.spawn((A(3), GhostNode::new()))
169190
.with_child((A(4), NodeBundle::default()));
170191
});
171192

172193
// Ghost root
173-
world.spawn((A(5), GhostNode)).with_children(|parent| {
174-
parent.spawn((A(6), NodeBundle::default()));
175-
parent
176-
.spawn((A(7), GhostNode))
177-
.with_child((A(8), NodeBundle::default()))
178-
.with_child(A(9));
179-
});
194+
world
195+
.spawn((A(5), GhostNode::new()))
196+
.with_children(|parent| {
197+
parent.spawn((A(6), NodeBundle::default()));
198+
parent
199+
.spawn((A(7), GhostNode::new()))
200+
.with_child((A(8), NodeBundle::default()))
201+
.with_child(A(9));
202+
});
180203

181204
let mut system_state = SystemState::<(UiRootNodes, Query<&A>)>::new(world);
182205
let (ui_root_nodes, a_query) = system_state.get(world);
@@ -191,15 +214,15 @@ mod tests {
191214
let world = &mut World::new();
192215

193216
let n1 = world.spawn((A(1), NodeBundle::default())).id();
194-
let n2 = world.spawn((A(2), GhostNode)).id();
195-
let n3 = world.spawn((A(3), GhostNode)).id();
217+
let n2 = world.spawn((A(2), GhostNode::new())).id();
218+
let n3 = world.spawn((A(3), GhostNode::new())).id();
196219
let n4 = world.spawn((A(4), NodeBundle::default())).id();
197220
let n5 = world.spawn((A(5), NodeBundle::default())).id();
198221

199-
let n6 = world.spawn((A(6), GhostNode)).id();
200-
let n7 = world.spawn((A(7), GhostNode)).id();
222+
let n6 = world.spawn((A(6), GhostNode::new())).id();
223+
let n7 = world.spawn((A(7), GhostNode::new())).id();
201224
let n8 = world.spawn((A(8), NodeBundle::default())).id();
202-
let n9 = world.spawn((A(9), GhostNode)).id();
225+
let n9 = world.spawn((A(9), GhostNode::new())).id();
203226
let n10 = world.spawn((A(10), NodeBundle::default())).id();
204227

205228
let no_ui = world.spawn_empty().id();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//! Experimental features are not yet stable and may change or be removed in the future.
2+
//!
3+
//! These features are not recommended for production use, but are available to ease experimentation
4+
//! within Bevy's ecosystem. Please let us know how you are using these features and what you would
5+
//! like to see improved!
6+
//!
7+
//! These may be feature-flagged: check the `Cargo.toml` for `bevy_ui` to see what options
8+
//! are available.
9+
//!
10+
//! # Warning
11+
//!
12+
//! Be careful when using these features, especially in concert with third-party crates,
13+
//! as they may not be fully supported, functional or stable.
14+
15+
mod ghost_hierarchy;
16+
17+
pub use ghost_hierarchy::*;

crates/bevy_ui/src/layout/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
2+
experimental::{UiChildren, UiRootNodes},
23
BorderRadius, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis,
3-
ScrollPosition, Style, TargetCamera, UiChildren, UiRootNodes, UiScale,
4+
ScrollPosition, Style, TargetCamera, UiScale,
45
};
56
use bevy_ecs::{
67
change_detection::{DetectChanges, DetectChangesMut},

crates/bevy_ui/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ pub mod picking_backend;
2323
use bevy_derive::{Deref, DerefMut};
2424
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
2525
mod accessibility;
26+
// This module is not re-exported, but is instead made public.
27+
// This is intended to discourage accidental use of the experimental API.
28+
pub mod experimental;
2629
mod focus;
2730
mod geometry;
28-
mod ghost_hierarchy;
2931
mod layout;
3032
mod render;
3133
mod stack;
3234
mod ui_node;
3335

3436
pub use focus::*;
3537
pub use geometry::*;
36-
pub use ghost_hierarchy::*;
3738
pub use layout::*;
3839
pub use measurement::*;
3940
pub use render::*;

crates/bevy_ui/src/stack.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
use bevy_ecs::prelude::*;
44
use bevy_utils::HashSet;
55

6-
use crate::{GlobalZIndex, Node, UiChildren, UiRootNodes, ZIndex};
6+
use crate::{
7+
experimental::{UiChildren, UiRootNodes},
8+
GlobalZIndex, Node, ZIndex,
9+
};
710

811
/// The current UI stack, which contains all UI nodes ordered by their depth (back-to-front).
912
///

crates/bevy_ui/src/update.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! This module contains systems that update the UI when something changes
22
3-
use crate::{CalculatedClip, Display, OverflowAxis, Style, TargetCamera, UiChildren, UiRootNodes};
3+
use crate::{
4+
experimental::{UiChildren, UiRootNodes},
5+
CalculatedClip, Display, OverflowAxis, Style, TargetCamera,
6+
};
47

58
use super::Node;
69
use bevy_ecs::{

0 commit comments

Comments
 (0)