Compare commits

..

No commits in common. '5724e670b78898ccb576e7f877fd8062591dff91' and '780e3aa52094aa099aa4251eafc576814a44cda9' have entirely different histories.

@ -1,2 +1 @@
00/camera.entity 00/a.entity
00/van.entity

@ -0,0 +1 @@
name "Hello world"

@ -1,2 +0,0 @@
camera
transform translation 2.0 2.0 2.0 ...

@ -1,3 +0,0 @@
; spatial3d
; transform ...
; pointLight color #ffffff intensity 800

@ -1,3 +0,0 @@
transform ...
visible
model "models/van.glb" "Scene"

@ -22,7 +22,6 @@ mkShell rec {
vulkan-loader # Rendering vulkan-loader # Rendering
xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature
tmux # Sharing environemnt between editor and terminal tmux # Sharing environemnt between editor and terminal
git-bug
]; ];
LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;

@ -32,13 +32,7 @@ fn main() {
// .add_plugins(menu::MenuPlugin) // .add_plugins(menu::MenuPlugin)
// .add_plugins(window::WindowPlugin) // .add_plugins(window::WindowPlugin)
.add_plugins(save::SavePlugin { .add_plugins(save::SavePlugin {
fns: vec![ fns: vec![parse_save_name],
parse_save_name,
parse_save_camera,
parse_save_visibility,
parse_save_transform,
parse_save_model,
],
}) })
.run(); .run();
} }

@ -1,15 +1,3 @@
use bevy::{
core_pipeline::{
core_3d::graph::Core3d,
tonemapping::{DebandDither, Tonemapping},
},
render::{
camera::{CameraMainTextureUsages, CameraRenderGraph, Exposure},
primitives::Frustum,
view::{ColorGrading, VisibleEntities},
},
};
use crate::prelude::*; use crate::prelude::*;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -33,8 +21,6 @@ pub(crate) enum Token {
Tag(String), Tag(String),
Str(String), Str(String),
Num(f32), Num(f32),
Comment(String),
Etc,
} }
/// ///
@ -44,35 +30,22 @@ pub(crate) fn tokenize(line: &str) -> Vec<Token> {
let mut l = line; let mut l = line;
let mut tokens = Vec::new(); let mut tokens = Vec::new();
// Check for comment
if let Ok((_, (_start, content))) = tuple((char::<&str, ()>(';'), not_line_ending))(line) {
tokens.push(Token::Comment(content.strip_prefix(" ").unwrap().into()));
} else {
// Check for all other token types in a loop
while l.len() > 0 { while l.len() > 0 {
if let Ok((rem, (_, _, s, _, _))) = tuple(( debug!("Line: {:?}", l);
space0, if let Ok((rem, (_, s, _))) = tuple((
char::<&str, ()>('"'), char::<&str, ()>('"'),
take_until("\""), take_until("\""),
char::<&str, ()>('"'), char::<&str, ()>('"'),
space0,
))(l) ))(l)
{ {
debug!("Parsed string {:?}", s); debug!("Parsed string {:?}", s);
tokens.push(Token::Str(s.into())); tokens.push(Token::Str(s.into()));
l = rem; l = rem;
} else if let Ok((rem, (_, num, _))) = tuple((space0, float::<&str, ()>, space0))(l) { } else if let Ok((rem, num)) = float::<&str, ()>(l) {
debug!("Parsed float {:?}", num); debug!("Parsed float {:?}", num);
tokens.push(Token::Num(num.into())); tokens.push(Token::Num(num.into()));
l = rem; l = rem;
} else if let Ok((rem, (_, etc, _))) = } else if let Ok((rem, (_, tag, _))) = tuple((space0, alphanumeric1::<&str, ()>, space0))(l)
tuple((space0, tag::<&str, &str, ()>("..."), space0))(l)
{
debug!("Parsed etc. {:?}", etc);
tokens.push(Token::Etc);
l = rem;
} else if let Ok((rem, (_, tag, _))) =
tuple((space0, alphanumeric1::<&str, ()>, space0))(l)
{ {
debug!("Parsed tag {:?}", tag); debug!("Parsed tag {:?}", tag);
tokens.push(Token::Tag(tag.into())); tokens.push(Token::Tag(tag.into()));
@ -82,8 +55,6 @@ pub(crate) fn tokenize(line: &str) -> Vec<Token> {
break; break;
} }
} }
}
debug!("Parsed tokens: {:?}", tokens);
tokens tokens
} }
@ -109,156 +80,34 @@ fn test_tokenize() {
/// ///
/// Returns reflected `Transform` /// Returns reflected `Transform`
/// ///
/// A fairly complicated parse function because Transform is sort of 3 components in one
/// (translation, rotation, scale).
///
/// It might get more complicated as I add more ways to express rotation!
///
pub(crate) fn parse_save_transform( pub(crate) fn parse_save_transform(
tokens: &Vec<Token>, tokens: &Vec<Token>,
) -> Result<Vec<Box<dyn Reflect>>, SaveEntityParseError> { ) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
if tokens.get(0) == Some(&Token::Tag("transform".into())) { // Tag(Transform),
let mut t = Transform::default(); // Tag(Translation), Number, Number, Number
let mut idx = 1; // Tag(Rotation), Number, Number, Number, Number
while idx < tokens.len() { // Tag(Scale), Number, Number, Number
match tokens.get(idx) {
Some(Token::Tag(attr)) => { // return Err(SaveEntityParseError::Transform);
idx += 1;
match attr.as_str() { todo!("parse_save_transform");
"translation" => {
if let Token::Num(x) = tokens[idx] {
t.translation.x = x;
idx += 1;
}
if let Token::Num(y) = tokens[idx] {
t.translation.y = y;
idx += 1;
}
if let Token::Num(z) = tokens[idx] {
t.translation.z = z;
idx += 1;
}
info!("{:?}", t.translation);
}
"scale" => {
if let Token::Num(x) = tokens[idx] {
t.scale.x = x;
idx += 1;
}
if let Token::Num(y) = tokens[idx] {
t.scale.y = y;
idx += 1;
}
if let Token::Num(z) = tokens[idx] {
t.scale.z = z;
idx += 1;
}
}
"rotation" => {
let x = match tokens.get(idx) {
Some(Token::Num(x)) => {
idx += 1;
*x
}
_ => 0.0,
};
let y = match tokens.get(idx) {
Some(Token::Num(y)) => {
idx += 1;
*y
}
_ => 0.0,
};
let z = match tokens.get(idx) {
Some(Token::Num(z)) => {
idx += 1;
*z
}
_ => 0.0,
};
let w = match tokens.get(idx) {
Some(Token::Num(w)) => {
idx += 1;
*w
}
_ => 1.0,
};
t.rotation = Quat::from_xyzw(x, y, z, w);
}
_ => idx += 1,
}
}
_ => idx += 1,
}
}
println!("Parsed transform: {:?}", t);
Ok(vec![
t.clone_value(),
GlobalTransform::default().clone_value(),
])
} else {
return Err(SaveEntityParseError::Component("Transform".into()));
}
} }
#[test] #[test]
fn test_parse_transform() { fn test_parse_transform() {
{
let line = "transform translation 1.0 2.0 3.0 rotation 0.1 0.2 0.3 1.0 scale 1.1 1.2 1.3"; let line = "transform translation 1.0 2.0 3.0 rotation 0.1 0.2 0.3 1.0 scale 1.1 1.2 1.3";
let tokens = tokenize(line); let tokens = tokenize(line);
let parsed = parse_save_transform(&tokens).unwrap(); let parsed = parse_save_transform(&tokens).unwrap();
let t = Transform { let expected = Transform {
translation: Vec3::new(1.0, 2.0, 3.0), translation: Vec3::new(1.0, 2.0, 3.0),
rotation: Quat::from_xyzw(0.1, 0.2, 0.3, 1.0), rotation: Quat::from_xyzw(0.1, 0.2, 0.3, 1.0),
scale: Vec3::new(1.1, 1.2, 1.3), scale: Vec3::new(1.1, 1.2, 1.3),
}; };
let gt = GlobalTransform::default();
let expected = vec![t.clone_value(), gt.clone_value()];
parsed.iter().zip(expected).for_each(|(p, e)| {
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap());
});
}
{
let line = "transform translation ... rotation ... scale ...";
let tokens = tokenize(line);
let parsed = parse_save_transform(&tokens).unwrap();
let t = Transform::default();
let gt = GlobalTransform::default();
let expected = vec![t.clone_value(), gt.clone_value()];
parsed.iter().zip(expected).for_each(|(p, e)| {
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap());
});
}
{
let line = "transform ...";
let tokens = tokenize(line);
let parsed = parse_save_transform(&tokens).unwrap();
let t = Transform::default();
let gt = GlobalTransform::default();
let expected = vec![t.clone_value(), gt.clone_value()];
parsed.iter().zip(expected).for_each(|(p, e)| {
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap());
});
}
{
let line = "transform translation 1.0 ... rotation 2.0 ... scale 3.0 ...";
let tokens = tokenize(line);
let parsed = parse_save_transform(&tokens).unwrap();
let t = Transform {
translation: Vec3::new(1.0, 0.0, 0.0),
rotation: Quat::from_xyzw(2.0, 0.0, 0.0, 1.0),
scale: Vec3::new(3.0, 1.0, 1.0),
};
let gt = GlobalTransform::default();
let expected = vec![t.clone_value(), gt.clone_value()];
parsed.iter().zip(expected).for_each(|(p, e)| { assert!(expected
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); .clone_value()
}); .reflect_partial_eq(parsed.as_reflect())
} .unwrap());
} }
/// ///
@ -266,11 +115,11 @@ fn test_parse_transform() {
/// ///
pub(crate) fn parse_save_name( pub(crate) fn parse_save_name(
tokens: &Vec<Token>, tokens: &Vec<Token>,
) -> Result<Vec<Box<dyn Reflect>>, SaveEntityParseError> { ) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
if let Some((Token::Tag(t), &[Token::Str(ref s)])) = tokens.split_first() if let Some((Token::Tag(t), &[Token::Str(ref s)])) = tokens.split_first()
&& *t == String::from("name") && *t == String::from("name")
{ {
Ok(vec![Name::new(s.clone()).clone_value()]) Ok(Name::new(s.clone()).clone_value())
} else { } else {
Err(SaveEntityParseError::Component("Name".into())) Err(SaveEntityParseError::Component("Name".into()))
} }
@ -282,21 +131,23 @@ fn test_parse_name() {
let line = "name Van"; let line = "name Van";
let tokens = tokenize(line); let tokens = tokenize(line);
let parsed = parse_save_name(&tokens).unwrap(); let parsed = parse_save_name(&tokens).unwrap();
let expected = vec![Name::new("Van").clone_value()]; let expected = Name::new("Van");
parsed.iter().zip(expected).for_each(|(p, e)| { assert!(expected
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); .clone_value()
}); .reflect_partial_eq(parsed.as_reflect())
.unwrap());
} }
{ {
let line = "name Big Mike"; let line = "name Big Mike";
let tokens = tokenize(line); let tokens = tokenize(line);
let parsed = parse_save_name(&tokens).unwrap(); let parsed = parse_save_name(&tokens).unwrap();
let expected = vec![Name::new("Big Mike").clone_value()]; let expected = Name::new("Big Mike");
parsed.iter().zip(expected).for_each(|(p, e)| { assert!(expected
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); .clone_value()
}); .reflect_partial_eq(parsed.as_reflect())
.unwrap());
} }
} }
@ -320,24 +171,16 @@ impl Component for SaveModel {
/// ///
pub(crate) fn parse_save_model( pub(crate) fn parse_save_model(
tokens: &Vec<Token>, tokens: &Vec<Token>,
) -> Result<Vec<Box<dyn Reflect>>, SaveEntityParseError> { ) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
if tokens.get(0) == Some(&Token::Tag("model".into())) { todo!("parse_save_model");
if let Token::Str(gltf_file) = tokens.get(1).expect("model requires gltf file") {
if let Token::Str(scene_name) = tokens.get(2).expect("model requires scene name") { /*
Ok(vec![SaveModel { Ok(SaveModel {
gltf_file: gltf_file.into(), gltf_file: gltf_path.into(),
scene_name: scene_name.clone(), scene_name: scene_name.into(),
}
.clone_value()])
} else {
Err(SaveEntityParseError::Component("Model".into()))
}
} else {
Err(SaveEntityParseError::Component("Model".into()))
}
} else {
Err(SaveEntityParseError::Component("Model".into()))
} }
.clone_value())
*/
} }
#[test] #[test]
@ -345,17 +188,18 @@ fn test_parse_model() {
let line = "model \"models/foo.glb\" \"My Scene\""; let line = "model \"models/foo.glb\" \"My Scene\"";
let tokens = tokenize(line); let tokens = tokenize(line);
let parsed = parse_save_model(&tokens).unwrap(); let parsed = parse_save_model(&tokens).unwrap();
let expected = vec![SaveModel { let expected = SaveModel {
gltf_file: "models/foo.glb".into(), gltf_file: "models/foo.glb".into(),
scene_name: "My Scene".into(), scene_name: "My Scene".into(),
}]; };
parsed.iter().zip(expected).for_each(|(p, e)| { assert!(expected
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap()); .clone_value()
}); .reflect_partial_eq(parsed.as_reflect())
.unwrap());
} }
#[derive(Component, Debug, Default, PartialEq, Reflect, Clone)] #[derive(Debug, Default, PartialEq, Reflect, Clone)]
#[reflect_value(Component, Default, PartialEq)] #[reflect_value(Component, Default, PartialEq)]
pub(crate) enum SaveCameraRenderTarget { pub(crate) enum SaveCameraRenderTarget {
#[default] #[default]
@ -363,29 +207,24 @@ pub(crate) enum SaveCameraRenderTarget {
Window(PathBuf), Window(PathBuf),
} }
impl Component for SaveCameraRenderTarget {
const STORAGE_TYPE: StorageType = StorageType::Table;
fn register_component_hooks(hooks: &mut ComponentHooks) {
todo!("Assign Render Target")
}
}
/// ///
/// Returns a reflected `Camera3d` /// Returns a reflected `SaveCameraRenderTarget
/// ///
pub(crate) fn parse_save_camera( pub(crate) fn parse_save_camera(
tokens: &Vec<Token>, tokens: &Vec<Token>,
) -> Result<Vec<Box<dyn Reflect>>, SaveEntityParseError> { ) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
if tokens[0] == Token::Tag("camera".into()) { todo!("parse_save_camera");
Ok(vec![
Camera::default().clone_value(), // Nothing parsed well
CameraRenderGraph::new(Core3d).clone_value(), // Err(SaveEntityParseError::Camera)
Projection::default().clone_value(),
VisibleEntities::default().clone_value(),
Frustum::default().clone_value(),
Camera3d::default().clone_value(),
Tonemapping::default().clone_value(),
DebandDither::default().clone_value(),
ColorGrading::default().clone_value(),
Exposure::default().clone_value(),
CameraMainTextureUsages::default().clone_value(),
])
} else {
Err(SaveEntityParseError::Component("camera".into()))
}
} }
#[test] #[test]
@ -394,92 +233,44 @@ fn test_parse_camera() {
let line = "camera"; let line = "camera";
let tokens = tokenize(line); let tokens = tokenize(line);
let parsed = parse_save_camera(&tokens).unwrap(); let parsed = parse_save_camera(&tokens).unwrap();
let expected = vec![ let expected = SaveCameraRenderTarget::Default;
Camera::default().clone_value(), assert!(expected
CameraRenderGraph::new(Core3d).clone_value(), .clone_value()
Projection::default().clone_value(), .reflect_partial_eq(parsed.as_reflect())
VisibleEntities::default().clone_value(), .unwrap());
Frustum::default().clone_value(), }
Camera3d::default().clone_value(), {
Tonemapping::default().clone_value(), let line = "camera target window";
DebandDither::default().clone_value(), let tokens = tokenize(line);
ColorGrading::default().clone_value(), let parsed = parse_save_camera(&tokens).unwrap();
Exposure::default().clone_value(), let expected = SaveCameraRenderTarget::Default;
CameraMainTextureUsages::default().clone_value(), assert!(expected
]; .clone_value()
parsed .reflect_partial_eq(parsed.as_reflect())
.iter() .unwrap());
.zip(expected) }
.for_each(|(p, e)| match e.reflect_partial_eq(p.as_reflect()) {
Some(r) => assert!(r),
None => warn!(
"Type {:?} does not support reflection",
e.get_represented_type_info().unwrap()
),
});
}
// {
// let line = "camera target window";
// let tokens = tokenize(line);
// let parsed = parse_save_camera(&tokens).unwrap();
// let expected = SaveCameraRenderTarget::Default;
// assert!(expected
// .clone_value()
// .reflect_partial_eq(parsed.as_reflect())
// .unwrap());
// }
// {
// let line = "camera target";
// let tokens = tokenize(line);
// let parsed = parse_save_camera(&tokens);
// assert!(parsed.is_err());
// }
// {
// let line = "camera target window \"some.entity\"";
// let tokens = tokenize(line);
// let parsed = parse_save_camera(&tokens).unwrap();
// let expected = SaveCameraRenderTarget::Window("some.entity".into());
// assert!(expected
// .clone_value()
// .reflect_partial_eq(parsed.as_reflect())
// .unwrap());
// }
{ {
let line = "notcamera"; let line = "camera target";
let tokens = tokenize(line); let tokens = tokenize(line);
let parsed = parse_save_camera(&tokens); let parsed = parse_save_camera(&tokens);
assert!(parsed.is_err()); assert!(parsed.is_err());
} }
{
let line = "camera target window \"some.entity\"";
let tokens = tokenize(line);
let parsed = parse_save_camera(&tokens).unwrap();
let expected = SaveCameraRenderTarget::Window("some.entity".into());
assert!(expected
.clone_value()
.reflect_partial_eq(parsed.as_reflect())
.unwrap());
} }
{
/// encapsulates the spatial 3d bits of an entity let line = "notcamera";
pub(crate) fn parse_save_visibility(
tokens: &Vec<Token>,
) -> Result<Vec<Box<dyn Reflect>>, SaveEntityParseError> {
if tokens[0] == Token::Tag("visible".into()) {
Ok(vec![
Visibility::default().clone_value(),
InheritedVisibility::default().clone_value(),
ViewVisibility::default().clone_value(),
])
} else {
Err(SaveEntityParseError::Component("visibility".into()))
}
}
#[test]
fn test_parse_visibility() {
let line = "visible";
let tokens = tokenize(line); let tokens = tokenize(line);
let parsed = parse_save_visibility(&tokens).unwrap(); let parsed = parse_save_camera(&tokens);
let expected = vec![ assert!(parsed.is_err());
Visibility::default().clone_value(), }
InheritedVisibility::default().clone_value(),
ViewVisibility::default().clone_value(),
];
parsed.iter().zip(expected).for_each(|(p, e)| {
assert!(e.clone_value().reflect_partial_eq(p.as_reflect()).unwrap());
});
} }
/// SaveParent entity which is a reference to which entity this is a child of /// SaveParent entity which is a reference to which entity this is a child of

@ -10,8 +10,8 @@ pub(crate) use bevy::{
window::{PrimaryWindow, WindowCloseRequested, WindowRef}, window::{PrimaryWindow, WindowCloseRequested, WindowRef},
}; };
pub(crate) use nom::{ pub(crate) use nom::{
bytes::complete::{tag, take_until}, bytes::complete::take_until,
character::complete::{alphanumeric1, char, not_line_ending, space0}, character::complete::{char, space0, alphanumeric1},
number::complete::float, number::complete::float,
sequence::tuple, sequence::tuple,
}; };

@ -1,9 +1,7 @@
use std::any::TypeId;
use crate::prelude::*; use crate::prelude::*;
type ParseFn = type ParseFn =
for<'a> fn(&'a Vec<Token>) -> Result<Vec<Box<(dyn Reflect + 'static)>>, SaveEntityParseError>; for<'a> fn(&'a Vec<Token>) -> Result<Box<(dyn Reflect + 'static)>, SaveEntityParseError>;
/// Menu Plugin; contains parser functions /// Menu Plugin; contains parser functions
pub(crate) struct SavePlugin { pub(crate) struct SavePlugin {
@ -53,12 +51,7 @@ pub(crate) struct SaveScene {
// Ironically we basically want DynamicEntity: https://docs.rs/bevy/latest/bevy/scene/struct.DynamicEntity.html // Ironically we basically want DynamicEntity: https://docs.rs/bevy/latest/bevy/scene/struct.DynamicEntity.html
#[derive(Asset, TypePath, Default)] #[derive(Asset, TypePath, Default)]
pub(crate) struct SaveEntity { pub(crate) struct SaveEntity {
components: Vec<SaveEntityComponent>, components: Vec<Box<dyn Reflect>>,
}
struct SaveEntityComponent {
type_id: TypeId,
data: Box<dyn Reflect>,
} }
impl SaveEntity { impl SaveEntity {
@ -79,20 +72,10 @@ impl SaveEntity {
// Tokenize the line // Tokenize the line
let tokens = tokenize(line); let tokens = tokenize(line);
if matches!(tokens[0], Token::Comment(..)) {
debug!("Skipping parsing comment line {:?}", tokens);
} else {
// Run line against all parsers // Run line against all parsers
for f in fns { for f in fns {
if let Ok(p) = f(&tokens) { if let Ok(v) = f(&tokens) {
p.iter().for_each(|c| { entity.components.push(v);
// Bundle the Type ID with this entry for auditing purposes
let t = c.get_represented_type_info().unwrap().type_id();
entity.components.push(SaveEntityComponent {
type_id: t,
data: c.clone_value(),
});
});
good = true; good = true;
} }
} }
@ -104,38 +87,7 @@ impl SaveEntity {
"failed to parse component", "failed to parse component",
); );
} }
}
});
// Check for duplicate component types and emit an error
// TODO: It would be nice if this emitted a line refernece instead of a parsed struct!
let l = entity.components.len();
(0..l).for_each(|i| {
(i..l).for_each(|j| {
if i != j {
let c1 = &entity.components[i];
let c2 = &entity.components[j];
let t1 = c1.type_id;
let t2 = c2.type_id;
if cfg!(debug_assertions) {
debug_assert!(
t1 != t2,
"Duplicate components in {:?}: \n\t{:?}\n\t{:?}",
load_context.asset_path(),
c1.data,
c2.data
);
} else {
error!(
"Duplicate components in {:?}: \n\t{:?}\n\t{:?}",
load_context.asset_path(),
c1.data,
c2.data
);
}
}
}); });
});
Ok(entity) Ok(entity)
} }
} }
@ -211,37 +163,6 @@ impl AssetLoader for SaveSceneLoader {
.map(|path| load_context.load(path)) .map(|path| load_context.load(path))
.collect(); .collect();
// Assert there are no duplicate entities in the file
{
let l = entities.len();
(0..l).for_each(|i| {
(i..l).for_each(|j| {
if i != j {
let e1 = &entities[i];
let e2 = &entities[j];
if cfg!(debug_assertions) {
debug_assert!(
e1 != e2,
"Duplicate entities in scene {:?}:\n\t{:?}\n\t{:?}",
load_context.asset_path(),
e1,
e2
);
} else {
if e1 == e2 {
error!(
"Duplicate entities in scene {:?}:\n\t{:?}\n\t{:?}",
load_context.asset_path(),
e1,
e2
)
}
}
}
});
});
}
Ok(SaveScene { entities }) Ok(SaveScene { entities })
} }
@ -318,8 +239,8 @@ fn spawn_entities(
// Add each component to the entity // Add each component to the entity
debug!("Populating entity with components {:?}", entity); debug!("Populating entity with components {:?}", entity);
save_entity.components.iter().for_each(|item| { save_entity.components.iter().for_each(|component| {
e.insert_reflect(item.data.clone_value()); e.insert_reflect(component.clone_value());
}); });
}); });
} }

Loading…
Cancel
Save