Skip to content
2 changes: 1 addition & 1 deletion crates/bevy_feathers/src/controls/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pub struct FeathersToolButton;
impl FeathersToolButton {
fn scene(props: FeathersButtonProps) -> impl Scene {
bsn! {
:FeathersButton {
@FeathersButton {
@caption: {props.caption},
@variant: {props.variant},
@corners: {props.corners}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_feathers/src/controls/disclosure_toggle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl FeathersDisclosureToggle {
FocusIndicator
TabIndex(0)
Children [
:icon(icons::CHEVRON_RIGHT)
icon(icons::CHEVRON_RIGHT)
]
)
}
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl Default for FeathersMenuButtonProps {
impl FeathersMenuButton {
fn scene(props: FeathersMenuButtonProps) -> impl Scene {
bsn! {
:FeathersButton {
@FeathersButton {
@caption: {props.caption},
@variant: ButtonVariant::Normal,
@corners: {props.corners},
Expand All @@ -174,7 +174,7 @@ impl FeathersMenuButton {
Node {
flex_grow: 1.0,
},
:icon(icons::CHEVRON_DOWN),
icon(icons::CHEVRON_DOWN),
)) as Box<dyn SceneList>
} else {
Box::new(bsn_list!()) as Box<dyn SceneList>
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/number_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl Default for FeathersNumberInputProps {
impl FeathersNumberInput {
fn scene(props: FeathersNumberInputProps) -> impl Scene {
bsn! {
:FeathersTextInputContainer
:@FeathersTextInputContainer
ThemeBorderColor({props.sigil_color})
FeathersNumberInput
template_value(props.number_format)
Expand Down Expand Up @@ -108,7 +108,7 @@ impl FeathersNumberInput {
None => Box::new(bsn_list!()) as Box<dyn SceneList>
}
}
:FeathersTextInput {
@FeathersTextInput {
@max_characters: 20usize,
}
SelectAllOnFocus
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_feathers/src/controls/virtual_keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl<T: AsRef<str> + Clone + Send + Sync + 'static> VirtualKeyboard<T> {
let key_row = Vec::from_iter(row.into_iter().map(move |key| {
let key_clone = key.clone();
bsn! {
:FeathersButton
:@FeathersButton
Node {
flex_grow: 1.0,
}
Expand Down
110 changes: 58 additions & 52 deletions crates/bevy_scene/macros/src/bsn/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::bsn::types::{
Bsn, BsnConstructor, BsnEntry, BsnFields, BsnInheritedScene, BsnListRoot, BsnRelatedSceneList,
BsnRoot, BsnSceneFn, BsnSceneFnArg, BsnSceneFnArgs, BsnSceneListItem, BsnSceneListItems,
BsnType, BsnValue,
Bsn, BsnConstructor, BsnEntry, BsnFields, BsnListRoot, BsnRelatedSceneList, BsnRoot, BsnScene,
BsnSceneFn, BsnSceneFnArg, BsnSceneFnArgs, BsnSceneListItem, BsnSceneListItems, BsnType,
BsnValue,
};
use bevy_macro_utils::{fq_std::FQDefault, path_to_string};
use proc_macro2::TokenStream;
Expand Down Expand Up @@ -228,7 +228,6 @@ impl BsnEntry {
let value = _scene.get_or_insert_template::<#type_path>(_context);
*value = #type_path::#const_ident;
}),
BsnEntry::SceneExpression(block) => EntryResult::NewSceneImpl(quote! {{#block}}),
BsnEntry::TemplateConstructor(BsnConstructor {
type_path,
function,
Expand Down Expand Up @@ -258,54 +257,11 @@ impl BsnEntry {
let scenes = scene_list.0.to_tokens(ctx);
EntryResult::NewSceneImpl(quote! {
#bevy_scene::RelatedScenes::<<#relationship_path as #bevy_ecs::relationship::RelationshipTarget>
::Relationship, _>::new(#scenes)
::Relationship, _>::new(#scenes)
})
}
BsnEntry::SceneFn(func) => EntryResult::NewSceneImpl(func.to_tokens(ctx)),
BsnEntry::InheritedScene(s) => EntryResult::NewSceneImpl(match s {
BsnInheritedScene::Asset(lit) => quote! {
#bevy_scene::InheritSceneAsset::from(#lit)
},
BsnInheritedScene::Fn(func) => func.to_tokens(ctx),
BsnInheritedScene::Type(bsn_type) => {
// TODO: this can and should use a simpler codegen path than BsnType::to_patch_tokens,
// which imposes constraints like requiring the type to impl FromTemplate, and requiring
// enums to have VariantDefault.
let mut assignments = Vec::new();
let props = format_ident!("props");
let props_ref = format_ident!("props_ref");
let target = PatchTarget {
path: &[Member::Named(props_ref.clone())],
is_ref: true,
};
bsn_type.to_patch_tokens(ctx, &mut assignments, false, true, true, target)?;
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
};
bsn_type.to_patch_tokens(ctx, &mut assigns, true, false, true, target)?;
let path = &bsn_type.path;
let bevy_scene = ctx.bevy_scene;
let from_template_patch = quote! {
<#path as #bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
#(#assigns)*
})
};
quote! {{
let mut #props = <<#path as #bevy_scene::SceneComponent>::Props as #FQDefault>::default();
let #props_ref = &mut #props;
#(#assignments)*
(<#path as #bevy_scene::SceneComponent>::scene(#props), #from_template_patch)
}}
}
BsnInheritedScene::Expression(tokens) => quote! {
#tokens
},
}),
BsnEntry::UncachedScene(s) => EntryResult::NewSceneImpl(s.to_tokens(ctx)?),
BsnEntry::CachedScene(s) => EntryResult::NewSceneImpl(s.to_tokens(ctx)?),
BsnEntry::Name(ident) => {
let (name, index) = (ident.to_string(), ctx.entity_refs.get(ident.to_string()));
let invocation = ctx.invocation_index.clone();
Expand All @@ -321,6 +277,56 @@ impl BsnEntry {
}
}

impl BsnScene {
fn to_tokens(&self, ctx: &mut BsnCodegenCtx) -> syn::Result<TokenStream> {
let bevy_scene = ctx.bevy_scene;
match self {
BsnScene::Asset(lit) => Ok(quote! {
#bevy_scene::CachedSceneAsset::from(#lit)
}),
BsnScene::Fn(func) => Ok(func.to_tokens(ctx)),
BsnScene::SceneComponent(bsn_type) => {
// TODO: this can and should use a simpler codegen path than BsnType::to_patch_tokens,
// which imposes constraints like requiring the type to impl FromTemplate, and requiring
// enums to have VariantDefault.
let mut assignments = Vec::new();
let props = format_ident!("props");
let props_ref = format_ident!("props_ref");
let target = PatchTarget {
path: &[Member::Named(props_ref.clone())],
is_ref: true,
};
bsn_type.to_patch_tokens(ctx, &mut assignments, false, true, true, target)?;
let mut assigns = Vec::new();
let target = PatchTarget {
path: &[Member::Named(Ident::new(
"value",
proc_macro2::Span::call_site(),
))],
is_ref: true,
};
bsn_type.to_patch_tokens(ctx, &mut assigns, true, false, true, target)?;
let path = &bsn_type.path;
let bevy_scene = ctx.bevy_scene;
let from_template_patch = quote! {
<#path as #bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
#(#assigns)*
})
};
Ok(quote! {{
let mut #props = <<#path as #bevy_scene::SceneComponent>::Props as #FQDefault>::default();
let #props_ref = &mut #props;
#(#assignments)*
(<#path as #bevy_scene::SceneComponent>::scene(#props), #from_template_patch)
}})
}
BsnScene::Expression(tokens) => Ok(quote! {
#tokens
}),
}
}
}

impl BsnType {
/// Recursively generates token streams.
fn to_patch_tokens(
Expand Down Expand Up @@ -447,8 +453,8 @@ impl BsnType {
format!(
"Scene prop fields are not supported in normal component patches\
. If you would like to set a component scene's prop field, it \
should be set using \"scene inheritance\": \
bsn! {{ :{} {{ @{field_name}: VALUE }} }}",
should be set using \"scene component\" syntax: \
bsn! {{ @{} {{ @{field_name}: VALUE }} }}",
path_to_string(type_path)
),
));
Expand Down
105 changes: 71 additions & 34 deletions crates/bevy_scene/macros/src/bsn/parse.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::bsn::types::{
Bsn, BsnConstructor, BsnEntry, BsnFields, BsnInheritedScene, BsnListRoot, BsnNamedField,
BsnRelatedSceneList, BsnRoot, BsnSceneFn, BsnSceneFnArg, BsnSceneFnArgs, BsnSceneList,
BsnSceneListItem, BsnSceneListItems, BsnTuple, BsnType, BsnUnnamedField, BsnValue,
Bsn, BsnConstructor, BsnEntry, BsnFields, BsnListRoot, BsnNamedField, BsnRelatedSceneList,
BsnRoot, BsnScene, BsnSceneFn, BsnSceneFnArg, BsnSceneFnArgs, BsnSceneList, BsnSceneListItem,
BsnSceneListItems, BsnTuple, BsnType, BsnUnnamedField, BsnValue,
};
use bevy_macro_utils::{path_to_string, PathType};
use proc_macro2::{Delimiter, TokenStream, TokenTree};
Expand All @@ -12,7 +12,7 @@ use syn::{
parenthesized,
parse::{Parse, ParseBuffer, ParseStream},
spanned::Spanned,
token::{At, Brace, Bracket, Colon, Comma, Paren},
token::{At, Brace, Bracket, Colon, Comma, Paren, Tilde},
Block, Expr, Ident, Lit, LitStr, Path, Result, Token,
};

Expand Down Expand Up @@ -59,22 +59,22 @@ impl Parse for BsnListRoot {
impl<const ALLOW_FLAT: bool> Parse for Bsn<ALLOW_FLAT> {
fn parse(input: ParseStream) -> Result<Self> {
let mut entries = Vec::new();
let mut found_inherited_scene = false;
let mut found_cached_scene = false;
if input.peek(Paren) {
let content;
parenthesized![content in input];
while !content.is_empty() {
let entry = BsnEntry::parse(&content, found_inherited_scene)?;
if matches!(entry, BsnEntry::InheritedScene(_)) {
found_inherited_scene = true;
let entry = BsnEntry::parse(&content, found_cached_scene)?;
if matches!(entry, BsnEntry::CachedScene(_)) {
found_cached_scene = true;
}
entries.push(entry);
}
} else if ALLOW_FLAT {
while !input.is_empty() {
let entry = BsnEntry::parse(input, found_inherited_scene)?;
if matches!(entry, BsnEntry::InheritedScene(_)) {
found_inherited_scene = true;
let entry = BsnEntry::parse(input, found_cached_scene)?;
if matches!(entry, BsnEntry::CachedScene(_)) {
found_cached_scene = true;
}
entries.push(entry);
if input.peek(Comma) {
Expand All @@ -84,30 +84,30 @@ impl<const ALLOW_FLAT: bool> Parse for Bsn<ALLOW_FLAT> {
}
}
} else {
entries.push(BsnEntry::parse(input, found_inherited_scene)?);
entries.push(BsnEntry::parse(input, found_cached_scene)?);
}

Ok(Self { entries })
}
}

impl BsnEntry {
fn parse(input: ParseStream, found_inherited_scene: bool) -> Result<Self> {
fn parse(input: ParseStream, found_cached_scene: bool) -> Result<Self> {
Ok(if input.peek(Token![:]) {
BsnEntry::InheritedScene(BsnInheritedScene::parse(input, found_inherited_scene)?)
BsnEntry::CachedScene(BsnScene::parse(input, found_cached_scene)?)
} else if input.peek(Token![#]) {
input.parse::<Token![#]>()?;
if input.peek(Brace) {
BsnEntry::NameExpression(braced_tokens(input)?)
} else {
BsnEntry::Name(input.parse::<Ident>()?)
}
} else if input.peek(Brace) {
BsnEntry::SceneExpression(braced_tokens(input)?)
} else if input.peek(Brace) || input.peek(At) {
BsnEntry::UncachedScene(BsnScene::parse(input, found_cached_scene)?)
} else {
let is_template = input.peek(At);
let is_template = input.peek(Tilde);
if is_template {
input.parse::<At>()?;
input.parse::<Tilde>()?;
}
let mut path = input.parse::<Path>()?;
let path_type = PathType::new(&path);
Expand Down Expand Up @@ -168,9 +168,9 @@ impl BsnEntry {
PathType::Function => {
if input.peek(Paren) {
let args = input.parse()?;
BsnEntry::SceneFn(BsnSceneFn { path, args })
BsnEntry::UncachedScene(BsnScene::Fn(BsnSceneFn { path, args }))
} else {
BsnEntry::SceneExpression(quote! {#path})
BsnEntry::UncachedScene(BsnScene::Expression(quote! {#path}))
}
}
}
Expand Down Expand Up @@ -231,39 +231,76 @@ impl Parse for BsnSceneFnArg {
}
}
}
impl BsnInheritedScene {
fn parse(input: ParseStream, found_inherited_scene: bool) -> Result<Self> {
let colon = input.parse::<Token![:]>()?;
if found_inherited_scene {
return Err(syn::Error::new(
colon.span(),
"Cannot inherit scenes more than once",
));
impl BsnScene {
fn parse(input: ParseStream, found_cached_scene: bool) -> Result<Self> {
let cached = if input.peek(Token![:]) {
Some(input.parse::<Token![:]>()?)
} else {
None
};

let err_if_cached = |msg: &str| {
if let Some(colon) = cached {
Err(syn::Error::new(colon.span(), msg))
} else {
Ok(())
}
};

if found_cached_scene {
err_if_cached("Cannot cache scenes more than once")?;
}

Ok(if input.peek(LitStr) {
let path = input.parse::<LitStr>()?;
BsnInheritedScene::Asset(path)
if cached.is_none() {
return Err(syn::Error::new(
path.span(),
"Cannot use scenes from asset path without caching, please add the ':' prefix.",
));
}
BsnScene::Asset(path)
} else if input.peek(Brace) {
BsnInheritedScene::Expression(braced_tokens(input)?)
err_if_cached("Cannot cache scene expressions")?;
BsnScene::Expression(braced_tokens(input)?)
} else if input.peek(At) {
input.parse::<At>()?;
let sc = input.parse::<BsnType>()?;
if sc.fields.len() > 0 {
err_if_cached("Cannot cache Scene Components with props/fields")?;
}
BsnScene::SceneComponent(sc)
} else {
// PERF: do we really need this fork here?
let path = input.fork().parse::<Path>()?;
match PathType::new(&path) {
PathType::Type | PathType::Enum => {
BsnInheritedScene::Type(input.parse::<BsnType>()?)
// Scene components are parsed before this if an @ is found.
// If this path is hit, that means it wasn't prefixed by @
return Err(syn::Error::new(
path.span(),
format!(
"Scene component {} needs to be prefixed by '@'",
path_to_string(&path),
),
));
}
PathType::Function | PathType::TypeFunction => {
let path = input.parse::<Path>()?;
BsnInheritedScene::Fn(BsnSceneFn {
let func = BsnSceneFn {
path,
args: input.parse()?,
})
};
if func.args.0.is_some() {
err_if_cached("Cannot cache Scene function with arguments")?;
}
BsnScene::Fn(func)
}
path_type => {
return Err(syn::Error::new(
path.span(),
format!(
"Cannot inherit from path {} of type {:?}",
"Cannot cache path {} of type {:?}",
path_to_string(&path),
path_type,
),
Expand Down
Loading
Loading