Skip to content

Commit 3e1dad5

Browse files
authored
Improve BSN macros and add ICBINBSN-like scene retention (#181)
* Simplify Scene to get rid of SceneTuple. Tuples of Scene now implements Scene * Add construct_from_patch extension for EntityWorldMut * Clean up bounds * Improve DynamicScene to handle concrete/typed component construction without reflection * Use new spawn API's PR as bevy version temporarily + migrate ConstructTuple bundle impl * Revert spawn apis for now * Add RetainScene partially implementing ICBINBSN receipts equivalent * Replace ICBINBSN hot example with RetainScene version * Clean up/improve Scene and DynamicPatch traits and impls * Adjust patch fn bounds, add retain_child_scenes, and port ICBINBSN scene tree example * WIP work MAYBE REVERT * Cleanup * Consume scenes instead of cloning them * Add spread operator * Add no_reflect attribute for non reflectable construct derives * Add On utility for bsn observers * Clean up examples * Update docs/examples + rename stuff * Avoid props ident conflicts in patch closures * Clean up required components properly when retaining scenes
1 parent 0052c78 commit 3e1dad5

21 files changed

+1203
-330
lines changed

crates/bevy_bsn/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,9 @@ bevy = { workspace = true }
1111
thiserror = { workspace = true }
1212
variadics_please = { workspace = true }
1313

14-
[dev-dependencies]
15-
bevy_i-cant-believe-its-not-bsn.workspace = true
16-
1714
[lints]
1815
workspace = true
1916

2017
[[example]]
21-
name = "bsn_icbinbsn_hot"
18+
name = "bsn_asset_hot"
2219
required-features = ["bevy/file_watcher"]

crates/bevy_bsn/examples/bsn_asset.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,7 @@ fn spawn_scene_on_load(
3939
.reflect_dynamic_scene()
4040
.unwrap();
4141

42-
commands.queue(move |world: &mut World| {
43-
let id = world.spawn_empty().id();
44-
dynamic_scene
45-
.construct(&mut ConstructContext::new(id, world))
46-
.unwrap();
47-
});
42+
commands.spawn_scene(dynamic_scene);
4843
};
4944
}
5045
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! Simple non-destructive .bsn-hot-reloading utilizing [`RetainScene`].
2+
//!
3+
//! Run with `--features="bevy/file_watcher"` to enable hot-reloading.
4+
use bevy::prelude::*;
5+
use bevy_bsn::*;
6+
7+
fn main() {
8+
App::new()
9+
.add_plugins(DefaultPlugins)
10+
.add_plugins(BsnPlugin)
11+
.add_systems(
12+
Startup,
13+
|mut commands: Commands, assets: Res<AssetServer>| {
14+
commands.spawn(Camera2d);
15+
16+
let bsn = assets.load::<Bsn>("hello.bsn");
17+
commands.spawn(SceneRoot(bsn));
18+
},
19+
)
20+
.add_systems(Update, spawn_and_reload_scene)
21+
.run();
22+
}
23+
24+
#[derive(Component)]
25+
struct SceneRoot(Handle<Bsn>);
26+
27+
fn spawn_and_reload_scene(
28+
scene_root: Single<Entity, With<SceneRoot>>,
29+
mut events: EventReader<AssetEvent<Bsn>>,
30+
bsn_assets: Res<Assets<Bsn>>,
31+
app_registry: Res<AppTypeRegistry>,
32+
mut commands: Commands,
33+
) {
34+
for event in events.read() {
35+
match event {
36+
AssetEvent::Added { id } | AssetEvent::Modified { id } => {
37+
let bsn = bsn_assets.get(*id).unwrap();
38+
39+
let registry = app_registry.read();
40+
let dynamic_scene = BsnReflector::new(bsn, &registry)
41+
.reflect_dynamic_scene()
42+
.unwrap();
43+
44+
commands.entity(*scene_root).retain_scene(dynamic_scene);
45+
}
46+
_ => {}
47+
}
48+
}
49+
}

crates/bevy_bsn/examples/bsn_icbinbsn_hot.rs

Lines changed: 0 additions & 110 deletions
This file was deleted.

crates/bevy_bsn/examples/bsn_macro.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn ui() -> impl Scene {
2323
flex_direction: FlexDirection::Column,
2424
row_gap: px(5.0),
2525
} [
26-
(Node, :button("Basic")),
26+
(Node, {Name::new("BasicButton")}, :button("Basic")),
2727
(Node, :button("Rounded"), rounded),
2828
(Node { border: px_all(5.0) }, BorderColor(RED_500) :button("Thick red"), rounded),
2929
(Node, :button("Merged children"), rounded) [(
@@ -34,6 +34,18 @@ fn ui() -> impl Scene {
3434
BackgroundColor(BLUE_500),
3535
{BorderRadius::MAX}
3636
)],
37+
38+
(:button("Click me!")) [
39+
// Observing parent entity
40+
On(|_: Trigger<Pointer<Click>>| {
41+
info!("Clicked me!");
42+
})
43+
],
44+
45+
// Observing entity "BasicButton" by name
46+
On(|_: Trigger<Pointer<Click>>| {
47+
info!("Clicked Basic!");
48+
}, @"BasicButton"),
3749
]
3850
}
3951
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! Super Sheep-Counter 2000
2+
//!
3+
//! An all-in-one numerical ruminant package.
4+
//!
5+
//! This example is originally from `i-cant-believe-its-not-bsn` and shows the differences between using `bsn!` and `template!`.
6+
use bevy::{color::palettes::css, prelude::*};
7+
8+
use bevy_bsn::{Scene, *};
9+
10+
fn main() {
11+
App::new()
12+
.add_plugins(DefaultPlugins)
13+
.add_plugins(BsnPlugin)
14+
.add_plugins(sheep_plugin)
15+
.run();
16+
}
17+
18+
fn sheep_plugin(app: &mut App) {
19+
app.add_systems(Startup, setup)
20+
.add_systems(Update, sheep_system);
21+
}
22+
23+
fn setup(mut commands: Commands) {
24+
commands.spawn(Camera2d);
25+
commands.spawn(UiRoot);
26+
}
27+
28+
#[derive(Component)]
29+
struct UiRoot;
30+
31+
#[derive(Component)]
32+
struct Sheep;
33+
34+
// A query that pulls data from the ecs and then updates it using a template.
35+
fn sheep_system(mut commands: Commands, sheep: Query<&Sheep>, root: Single<Entity, With<UiRoot>>) {
36+
let num_sheep = sheep.iter().len();
37+
38+
let template = bsn! {
39+
Node {
40+
position_type: PositionType::Absolute,
41+
bottom: Val::Px(5.0),
42+
right: Val::Px(5.0),
43+
} [
44+
..counter(num_sheep, "sheep"),
45+
]
46+
};
47+
48+
commands.entity(*root).retain_scene(template);
49+
}
50+
51+
// A function that returns an ecs template.
52+
fn counter(num: usize, name: &'static str) -> impl Scene {
53+
bsn! {
54+
Node [
55+
Text("You have ") [
56+
TextSpan(format!("{num}")),
57+
TextSpan(format!(" {name}!")),
58+
],
59+
(
60+
Button,
61+
Text("Increase"),
62+
TextColor(css::GREEN),
63+
{visible_if(num < 100)}
64+
) [
65+
// Observes parent entity.
66+
On(|_: Trigger<Pointer<Released>>, mut commands: Commands| {
67+
commands.spawn(Sheep);
68+
})
69+
],
70+
(
71+
{Name::new("DecreaseButton")},
72+
Button,
73+
Text("Decrease"),
74+
TextColor(css::RED),
75+
{visible_if(num > 0)},
76+
),
77+
// Observes named entity "DecreaseButton"
78+
On(|_: Trigger<Pointer<Released>>, sheep: Query<Entity, With<Sheep>>, mut commands: Commands| {
79+
if let Some(sheep) = sheep.iter().next() {
80+
commands.entity(sheep).despawn();
81+
}
82+
}, @"DecreaseButton"),
83+
]
84+
}
85+
}
86+
87+
// A component helper function for computing visibility.
88+
fn visible_if(condition: bool) -> Visibility {
89+
if condition {
90+
Visibility::Visible
91+
} else {
92+
Visibility::Hidden
93+
}
94+
}

0 commit comments

Comments
 (0)