use crate::{prelude::*, tiled::properties::types_json::*};
use bevy::{
ecs::reflect::ReflectBundle,
platform::collections::HashMap,
prelude::*,
reflect::{
array::ArrayInfo,
enums::{EnumInfo, VariantInfo},
list::ListInfo,
map::MapInfo,
set::SetInfo,
structs::StructInfo,
tuple::TupleInfo,
tuple_struct::TupleStructInfo,
NamedField, ReflectRef, TypeInfo, TypeRegistration, TypeRegistry, UnnamedField,
},
};
use serde_json::Value;
use std::borrow::Cow;
use thiserror::Error;
const DEFAULT_COLOR: &str = "#000000";
const USE_AS_PROPERTY: &[UseAs] = &[UseAs::Property];
type ExportConversionResult = Result<Vec<TypeExport>, ExportConversionError>;
#[derive(Debug, Eq, PartialEq, Copy, Clone, Error)]
enum ExportConversionError {
#[error("field of type {0} is not supported")]
UnsupportedValue(&'static str),
}
#[derive(Debug, Default, Clone)]
pub(crate) struct TypeExportRegistry {
types: HashMap<&'static str, Vec<TypeExport>>,
id: u32,
}
impl TypeExportRegistry {
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_vec(self) -> Vec<TypeExport> {
let mut out = self.types.into_values().flatten().collect::<Vec<_>>();
out.sort_by_key(|e| e.id);
out
}
pub(crate) fn from_registry(registry: &TypeRegistry, filter: &TiledFilter) -> Self {
let mut out = Self::default();
let mut sorted_registry = registry.iter().collect::<Vec<_>>();
sorted_registry.sort_by(|a, b| a.type_info().type_path().cmp(b.type_info().type_path()));
for top_level_type in sorted_registry {
let mut types_to_export: Vec<&TypeRegistration> = vec![];
if top_level_type.data::<ReflectComponent>().is_none()
&& top_level_type.data::<ReflectBundle>().is_none()
&& top_level_type.data::<ReflectResource>().is_none()
{
continue;
}
if !filter.matches(top_level_type.type_info().type_path()) {
continue;
}
if out
.types
.contains_key(top_level_type.type_info().type_path())
{
continue;
}
let deps = dependencies(top_level_type, registry);
for d in deps {
if let Some(dep_registration) = registry.get_with_type_path(d) {
if !out
.types
.contains_key(dep_registration.type_info().type_path())
{
types_to_export.push(dep_registration);
}
}
}
types_to_export.push(top_level_type);
if types_to_export.iter().any(|t| {
debug!("Exporting type '{}'", t.type_info().type_path());
match out.generate_export(t, registry, USE_AS_PROPERTY.to_vec()) {
Ok(export) => {
out.types.insert(t.type_info().type_path(), export);
false
}
Err(e) => {
warn!("Export error for '{}': {}", t.type_info().type_path(), e);
true
}
}
}) {
warn!(
"'{}' will not be exported",
top_level_type.type_info().type_path()
);
for t in types_to_export {
out.types.remove(t.type_info().type_path());
}
}
}
out
}
fn next_id(&mut self) -> u32 {
self.id += 1;
self.id
}
fn is_simple(registration: &TypeRegistration) -> bool {
matches!(
registration.type_info().type_path(),
"bevy_color::color::Color" | "bevy_ecs::name::Name"
)
}
fn generate_export(
&mut self,
registration: &TypeRegistration,
registry: &TypeRegistry,
use_as: Vec<UseAs>,
) -> ExportConversionResult {
let tmp = registration.data::<ReflectDefault>().map(|v| v.default());
let default_value = tmp.as_deref();
if Self::is_simple(registration) {
let (type_field, property_type) = type_to_field(registration)?;
return Ok(vec![TypeExport {
id: self.next_id(),
name: registration.type_info().type_path().to_string(),
type_data: TypeData::Class(Class {
use_as,
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "0".to_string(),
property_type,
type_field,
value: default_value
.map(|v| value_to_json(v.as_partial_reflect()))
.unwrap_or_default(),
}],
}),
}]);
}
match registration.type_info() {
TypeInfo::TupleStruct(info) => {
self.generate_tuple_struct_export(info, registry, default_value, use_as)
}
TypeInfo::Struct(info) => {
self.generate_struct_export(info, registry, default_value, use_as)
}
TypeInfo::Tuple(info) => {
self.generate_tuple_export(info, registry, default_value, use_as)
}
TypeInfo::List(info) => self.generate_list_export(info, registry, use_as),
TypeInfo::Array(info) => self.generate_array_export(info, registry, use_as),
TypeInfo::Map(info) => self.generate_map_export(info, registry, use_as),
TypeInfo::Enum(info) => {
self.generate_enum_export(info, registry, default_value, use_as)
}
TypeInfo::Opaque(_) => Ok(vec![]),
TypeInfo::Set(info) => self.generate_set_export(info, registry, use_as),
}
}
fn generate_tuple_struct_export(
&mut self,
info: &TupleStructInfo,
registry: &TypeRegistry,
default_value: Option<&dyn Reflect>,
_use_as: Vec<UseAs>,
) -> ExportConversionResult {
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: info
.iter()
.map(|s| {
let (type_field, property_type) =
type_to_field(registry.get(s.type_id()).unwrap())?;
Ok(Member {
name: s.index().to_string(),
property_type,
type_field,
value: unnamed_field_json_value(
default_value.map(|v| v.as_partial_reflect()),
s,
),
})
})
.collect::<Result<_, _>>()?,
}),
};
Ok(vec![root])
}
fn generate_array_export(
&mut self,
info: &ArrayInfo,
registry: &TypeRegistry,
use_as: Vec<UseAs>,
) -> ExportConversionResult {
let (type_field, property_type) =
type_to_field(registry.get(info.item_ty().id()).unwrap())?;
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as,
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: (0..info.capacity())
.map(|i| Member {
name: format!("[{i}]"),
property_type: property_type.clone(),
type_field,
value: Default::default(),
})
.collect(),
}),
};
Ok(vec![root])
}
fn generate_list_export(
&mut self,
info: &ListInfo,
registry: &TypeRegistry,
use_as: Vec<UseAs>,
) -> ExportConversionResult {
let item_registry =
registry
.get(info.item_ty().id())
.ok_or(ExportConversionError::UnsupportedValue(
info.item_ty().path(),
))?;
let default_value = item_registry.data::<ReflectDefault>().map(|d| d.default());
let (type_field, property_type) = type_to_field(item_registry)?;
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as,
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "list".to_string(),
property_type: None,
type_field: FieldType::List,
value: Value::Array(vec![serde_json::to_value(ListItem {
property_type: property_type.clone(),
type_field,
value: default_value
.map(|v| value_to_json(v.as_partial_reflect()))
.unwrap_or_default(),
})
.unwrap()]),
}],
}),
};
Ok(vec![root])
}
fn generate_map_export(
&mut self,
info: &MapInfo,
registry: &TypeRegistry,
use_as: Vec<UseAs>,
) -> ExportConversionResult {
let value_registry =
registry
.get(info.value_ty().id())
.ok_or(ExportConversionError::UnsupportedValue(
info.value_ty().path(),
))?;
let default_value = value_registry.data::<ReflectDefault>().map(|d| d.default());
let (value_type_field, value_property_type) = type_to_field(value_registry)?;
let key_registry =
registry
.get(info.key_ty().id())
.ok_or(ExportConversionError::UnsupportedValue(
info.key_ty().path(),
))?;
let default_key = key_registry.data::<ReflectDefault>().map(|d| d.default());
let (key_type_field, key_property_type) = type_to_field(key_registry)?;
let map_item_property_type = format!("{}:::MapItem", info.type_path());
let map_item = TypeExport {
id: self.next_id(),
name: map_item_property_type.clone(),
type_data: TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![
Member {
name: "key".to_string(),
property_type: key_property_type.clone(),
type_field: key_type_field,
value: default_key
.map(|v| value_to_json(v.as_partial_reflect()))
.unwrap_or_default(),
},
Member {
name: "value".to_string(),
property_type: value_property_type.clone(),
type_field: value_type_field,
value: default_value
.map(|v| value_to_json(v.as_partial_reflect()))
.unwrap_or_default(),
},
],
}),
};
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as,
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "map".to_string(),
property_type: None,
type_field: FieldType::List,
value: Value::Array(vec![serde_json::to_value(ListItem {
property_type: Some(map_item_property_type),
type_field: FieldType::Class,
value: Default::default(),
})
.unwrap()]),
}],
}),
};
Ok(vec![root, map_item])
}
fn generate_set_export(
&mut self,
info: &SetInfo,
registry: &TypeRegistry,
use_as: Vec<UseAs>,
) -> ExportConversionResult {
let value_registry =
registry
.get(info.value_ty().id())
.ok_or(ExportConversionError::UnsupportedValue(
info.value_ty().path(),
))?;
let default_value = value_registry.data::<ReflectDefault>().map(|d| d.default());
let (type_field, property_type) = type_to_field(value_registry)?;
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as,
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "set".to_string(),
property_type: None,
type_field: FieldType::List,
value: Value::Array(vec![serde_json::to_value(ListItem {
property_type: property_type.clone(),
type_field,
value: default_value
.map(|v| value_to_json(v.as_partial_reflect()))
.unwrap_or_default(),
})
.unwrap()]),
}],
}),
};
Ok(vec![root])
}
fn generate_tuple_export(
&mut self,
info: &TupleInfo,
registry: &TypeRegistry,
default_value: Option<&dyn Reflect>,
use_as: Vec<UseAs>,
) -> ExportConversionResult {
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as,
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: info
.iter()
.map(|s| {
let (type_field, property_type) =
type_to_field(registry.get(s.type_id()).unwrap())?;
Ok(Member {
name: s.index().to_string(),
property_type,
type_field,
value: unnamed_field_json_value(
default_value.map(|v| v.as_partial_reflect()),
s,
),
})
})
.collect::<Result<_, _>>()?,
}),
};
Ok(vec![root])
}
fn generate_struct_export(
&mut self,
info: &StructInfo,
registry: &TypeRegistry,
default_value: Option<&dyn Reflect>,
use_as: Vec<UseAs>,
) -> ExportConversionResult {
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as,
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: info
.iter()
.map(|s| {
let (type_field, property_type) =
type_to_field(registry.get(s.type_id()).unwrap())?;
Ok(Member {
name: s.name().to_string(),
property_type,
type_field,
value: named_field_json_value(
default_value.map(|v| v.as_partial_reflect()),
s,
),
})
})
.collect::<Result<_, _>>()?,
}),
};
Ok(vec![root])
}
fn generate_enum_export(
&mut self,
info: &EnumInfo,
registry: &TypeRegistry,
default_value: Option<&dyn Reflect>,
_use_as: Vec<UseAs>,
) -> ExportConversionResult {
let variants_name = info.type_path().to_string() + ":::Variant";
let mut out = vec![TypeExport {
id: self.next_id(),
name: variants_name.clone(),
type_data: TypeData::Enum(Enum {
storage_type: StorageType::String,
values_as_flags: false,
values: info.iter().map(|s| s.name().to_string()).collect(),
}),
}];
let mut root_members = Vec::with_capacity(2);
root_members.push(Member {
name: ":variant".to_string(),
property_type: Some(variants_name),
type_field: FieldType::String,
value: info
.iter()
.find(|variant| {
let Some(default_value) = default_value else {
return true;
};
match default_value.as_partial_reflect().reflect_ref() {
ReflectRef::Enum(value) => value.variant_name().eq(variant.name()),
_ => true,
}
})
.map(|s| serde_json::Value::String(s.name().to_string()))
.unwrap_or_default(),
});
for variant in info.iter() {
match variant {
VariantInfo::Struct(s) => {
let name = format!("{}::{}", info.type_path(), s.name());
let import = TypeExport {
id: self.next_id(),
name: name.clone(),
type_data: TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: s
.iter()
.map(|s| {
let (type_field, property_type) =
type_to_field(registry.get(s.type_id()).unwrap())?;
Ok(Member {
name: s.name().to_string(),
property_type,
type_field,
value: Default::default(),
})
})
.collect::<Result<_, _>>()?,
}),
};
out.push(import);
let root_field = Member {
name: s.name().to_string(),
property_type: Some(name),
type_field: FieldType::Class,
value: Default::default(),
};
root_members.push(root_field);
}
VariantInfo::Tuple(tuple) => {
let name = format!("{}::{}", info.type_path(), tuple.name());
let import = TypeExport {
id: self.next_id(),
name: name.clone(),
type_data: TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: tuple
.iter()
.map(|s| {
let (type_field, property_type) =
type_to_field(registry.get(s.type_id()).unwrap())?;
Ok(Member {
name: s.index().to_string(),
property_type,
type_field,
value: Default::default(),
})
})
.collect::<Result<_, _>>()?,
}),
};
out.push(import);
let root_field = Member {
name: tuple.name().to_string(),
property_type: Some(name),
type_field: FieldType::Class,
value: Default::default(),
};
root_members.push(root_field);
}
VariantInfo::Unit(_) => continue,
}
}
let root = TypeExport {
id: self.next_id(),
name: info.type_path().to_string(),
type_data: TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: root_members,
}),
};
out.push(root);
Ok(out)
}
}
fn value_to_json(value: &dyn PartialReflect) -> serde_json::Value {
let Some(type_info) = value.get_represented_type_info() else {
return serde_json::Value::default();
};
match (type_info.type_path(), type_info, value.reflect_ref()) {
("bool", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<bool>().unwrap())
}
("f32", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<f32>().unwrap())
}
("f64", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<f64>().unwrap())
}
("isize", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<isize>().unwrap())
}
("i8", _, ReflectRef::Opaque(v)) => serde_json::json!(*v.try_downcast_ref::<i8>().unwrap()),
("i16", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<i16>().unwrap())
}
("i32", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<i32>().unwrap())
}
("i64", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<i64>().unwrap())
}
("i128", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<i128>().unwrap())
}
("usize", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<usize>().unwrap())
}
("u8", _, ReflectRef::Opaque(v)) => serde_json::json!(*v.try_downcast_ref::<u8>().unwrap()),
("u16", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<u16>().unwrap())
}
("u32", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<u32>().unwrap())
}
("u64", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<u64>().unwrap())
}
("u128", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<u128>().unwrap())
}
("alloc::string::String", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<String>().unwrap())
}
("alloc::borrow::Cow<str>", _, ReflectRef::Opaque(v)) => {
serde_json::json!(*v.try_downcast_ref::<Cow<str>>().unwrap())
}
("bevy_platform::hash::Hashed<alloc::borrow::Cow<str>, bevy_platform::hash::FixedHasher>", _, ReflectRef::Opaque(_)) => {
serde_json::json!("")
}
("bevy_color::color::Color", _, _) => {
let c = value.try_downcast_ref::<Color>().unwrap();
serde_json::json!(format!("#{:08x}", c.to_linear().as_u32()))
}
(_, TypeInfo::Enum(_), ReflectRef::Enum(_)) => {
serde_json::json!({})
}
(_, TypeInfo::Struct(info), _) => info
.iter()
.map(|s| (s.name(), named_field_json_value(Some(value), s)))
.collect(),
(_, TypeInfo::Tuple(info), _) => info
.iter()
.map(|s| {
(
s.index().to_string(),
unnamed_field_json_value(Some(value), s),
)
})
.collect(),
(_, TypeInfo::TupleStruct(info), _) => info
.iter()
.map(|s| {
(
s.index().to_string(),
unnamed_field_json_value(Some(value), s),
)
})
.collect(),
(_, TypeInfo::List(_), _) => serde_json::Value::default(),
(_, TypeInfo::Map(_), _) => serde_json::Value::default(),
(_, TypeInfo::Set(_), _) => serde_json::Value::default(),
_ => {
warn!(
"cannot convert type '{}' to a JSON value",
type_info.type_path()
);
serde_json::Value::default()
}
}
}
fn named_field_json_value(
parent_value: Option<&dyn PartialReflect>,
field: &NamedField,
) -> serde_json::Value {
match parent_value {
Some(v) => match v.reflect_ref() {
ReflectRef::Struct(t) => t
.field(field.name())
.map(value_to_json)
.unwrap_or(serde_json::Value::default()),
_ => serde_json::Value::default(),
},
_ => serde_json::Value::default(),
}
}
fn unnamed_field_json_value(
parent_value: Option<&dyn PartialReflect>,
field: &UnnamedField,
) -> serde_json::Value {
match parent_value {
Some(v) => match v.reflect_ref() {
ReflectRef::TupleStruct(t) => (*t)
.field(field.index())
.map(value_to_json)
.unwrap_or(serde_json::Value::default()),
ReflectRef::Tuple(t) => (*t)
.field(field.index())
.map(value_to_json)
.unwrap_or(serde_json::Value::default()),
_ => serde_json::Value::default(),
},
_ => serde_json::Value::default(),
}
}
fn type_to_field(
t: &TypeRegistration,
) -> Result<(FieldType, Option<String>), ExportConversionError> {
let info = t.type_info();
Ok(match info.type_path() {
"bool" => (FieldType::Bool, None),
"f32" | "f64" => (FieldType::Float, None),
"isize" | "i8" | "i16" | "i32" | "i64" | "i128" | "usize" | "u8" | "u16" | "u32"
| "u64" | "u128" => (FieldType::Int, None),
"bevy_ecs::entity::Entity" | "core::option::Option<bevy_ecs::entity::Entity>" => {
(FieldType::Object, None)
}
"bevy_ecs::name::Name" | "alloc::borrow::Cow<str>" | "alloc::string::String" | "char" => {
(FieldType::String, None)
}
"bevy_color::color::Color" => (FieldType::Color, None),
"std::path::PathBuf" => (FieldType::File, None),
f if f.starts_with("bevy_asset::handle::Handle") => (FieldType::File, None),
path => {
if matches!(info, TypeInfo::Opaque(_)) {
return Err(ExportConversionError::UnsupportedValue(info.type_path()));
}
(FieldType::Class, Some(path.to_string()))
}
})
}
fn dependencies(registration: &TypeRegistration, registry: &TypeRegistry) -> Vec<&'static str> {
if TypeExportRegistry::is_simple(registration) {
return vec![];
}
let deps = match registration.type_info() {
TypeInfo::Struct(info) => info.iter().map(NamedField::type_path).collect(),
TypeInfo::TupleStruct(info) => info.iter().map(UnnamedField::type_path).collect(),
TypeInfo::Tuple(info) => info.iter().map(UnnamedField::type_path).collect(),
TypeInfo::List(info) => vec![info.item_ty().type_path_table().path()],
TypeInfo::Array(info) => vec![info.item_ty().type_path_table().path()],
TypeInfo::Map(info) => vec![
info.key_ty().type_path_table().path(),
info.value_ty().type_path_table().path(),
],
TypeInfo::Enum(info) => info
.iter()
.flat_map(|s| match s {
VariantInfo::Struct(s) => s.iter().map(NamedField::type_path).collect(),
VariantInfo::Tuple(s) => s.iter().map(UnnamedField::type_path).collect(),
VariantInfo::Unit(_) => vec![],
})
.collect(),
TypeInfo::Set(info) => vec![info.value_ty().type_path_table().path()],
TypeInfo::Opaque(_) => vec![],
};
let mut all_deps = deps.clone();
for d in deps {
if let Some(t) = registry.get_with_type_path(d) {
let mut new_deps = dependencies(t, registry);
new_deps.append(&mut all_deps);
all_deps = new_deps;
}
}
all_deps
}
#[cfg(test)]
mod tests {
use super::*;
use bevy::{
platform::collections::HashSet, render::render_resource::encase::rts_array::Length,
};
#[test]
fn generate_with_entity() {
#[derive(Component, Reflect)]
#[reflect(Component)]
struct ComponentA(Entity);
let mut registry = TypeRegistry::new();
registry.register::<ComponentA>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All);
let export_type = &exports.types.get(ComponentA::type_path()).unwrap();
assert_eq!(export_type.length(), 1);
assert_eq!(export_type[0].name, ComponentA::type_path().to_string());
assert_eq!(
export_type[0].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "0".to_string(),
property_type: None,
type_field: FieldType::Object,
value: Default::default(),
}],
}),
);
}
#[test]
fn generate_with_entity_option() {
#[derive(Component, Reflect)]
#[reflect(Component)]
struct ComponentA(Option<Entity>);
let mut registry = TypeRegistry::new();
registry.register::<ComponentA>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All);
let export_type = &exports.types.get(ComponentA::type_path()).unwrap();
assert_eq!(export_type.length(), 1);
assert_eq!(export_type[0].name, ComponentA::type_path().to_string());
assert_eq!(
export_type[0].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "0".to_string(),
property_type: None,
type_field: FieldType::Object,
value: Default::default(),
}],
}),
);
}
#[test]
fn generate_simple_enum() {
#[derive(Component, Reflect)]
#[reflect(Component)]
enum EnumComponent {
VarA,
VarB,
VarC,
}
let mut registry = TypeRegistry::new();
registry.register::<EnumComponent>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All);
let export_type = &exports.types.get(EnumComponent::type_path()).unwrap();
assert_eq!(export_type.length(), 2);
assert_eq!(
export_type[0].name,
EnumComponent::type_path().to_string() + ":::Variant"
);
assert_eq!(
export_type[0].type_data,
TypeData::Enum(Enum {
storage_type: StorageType::String,
values: vec!["VarA".to_string(), "VarB".to_string(), "VarC".to_string(),],
values_as_flags: false,
}),
);
assert_eq!(export_type[1].name, EnumComponent::type_path().to_string());
assert_eq!(
export_type[1].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: ":variant".to_string(),
property_type: Some(EnumComponent::type_path().to_string() + ":::Variant"),
type_field: FieldType::String,
value: serde_json::json!("VarA"),
},],
}),
);
}
#[test]
fn generate_nested_struct_with_default() {
#[derive(Reflect, Default)]
#[reflect(Default)]
enum TestEnum {
VarA,
#[default]
VarB,
VarC,
}
#[derive(Reflect)]
#[reflect(Default)]
struct InnerStruct {
another_float: f64,
another_integer: u16,
another_enum: TestEnum,
}
impl Default for InnerStruct {
fn default() -> Self {
Self {
another_float: 123.456,
another_integer: 42,
another_enum: TestEnum::VarC,
}
}
}
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default)]
struct StructComponent {
a_float: f32,
an_enum: TestEnum,
a_struct: InnerStruct,
an_integer: i32,
}
let mut registry = TypeRegistry::new();
registry.register::<TestEnum>();
registry.register::<InnerStruct>();
registry.register::<StructComponent>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All);
let export_type = &exports.types.get(StructComponent::type_path()).unwrap();
assert_eq!(export_type.length(), 1);
assert_eq!(
export_type[0].name,
StructComponent::type_path().to_string()
);
assert_eq!(
export_type[0].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![
Member {
name: "a_float".to_string(),
property_type: None,
type_field: FieldType::Float,
value: serde_json::json!(0.0),
},
Member {
name: "an_enum".to_string(),
property_type: Some(TestEnum::type_path().to_string()),
type_field: FieldType::Class,
value: serde_json::json!({}),
},
Member {
name: "a_struct".to_string(),
property_type: Some(InnerStruct::type_path().to_string()),
type_field: FieldType::Class,
value: serde_json::json!({
"another_enum": {},
"another_float": 123.456,
"another_integer": 42
})
},
Member {
name: "an_integer".to_string(),
property_type: None,
type_field: FieldType::Int,
value: serde_json::json!(0),
}
],
})
);
}
#[test]
fn generate_nested_tuple_struct() {
#[derive(Reflect, Default, Debug, Component)]
#[reflect(Default, Component)]
struct TestOuter(TestInner);
#[derive(Reflect, Default, Debug)]
struct TestInner(i32, [f32; 3]);
#[derive(Reflect, Default)]
struct TestStruct {
a: i32,
b: String,
}
#[derive(Reflect)]
enum TestVariant {
Apples,
Bananas,
Oranges,
}
let mut registry = TypeRegistry::new();
registry.register::<TestOuter>();
registry.register::<TestInner>();
registry.register::<TestStruct>();
registry.register::<TestVariant>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All);
let export_type = &exports.types.get(TestOuter::type_path()).unwrap();
assert_eq!(export_type.length(), 1);
assert_eq!(export_type[0].name, TestOuter::type_path().to_string());
assert_eq!(
export_type[0].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "0".to_string(),
property_type: Some(TestInner::type_path().to_string()),
type_field: FieldType::Class,
value: serde_json::json!({
"0": 0,
"1": serde_json::Value::default(),
})
}]
})
);
}
#[test]
fn generate_non_simple_enum() {
#[derive(Reflect)]
struct TestStruct {
a: i32,
b: String,
}
#[derive(Component, Reflect)]
#[reflect(Component)]
enum EnumComponent {
VarA,
VarB(u32),
VarC(TestStruct),
VarD((u16, f32)),
}
let mut registry = TypeRegistry::new();
registry.register::<TestStruct>();
registry.register::<EnumComponent>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All);
let export_type = &exports.types.get(EnumComponent::type_path()).unwrap();
assert_eq!(export_type.length(), 5);
assert_eq!(
export_type[0].name,
EnumComponent::type_path().to_string() + ":::Variant"
);
assert_eq!(
export_type[0].type_data,
TypeData::Enum(Enum {
storage_type: StorageType::String,
values: vec![
"VarA".to_string(),
"VarB".to_string(),
"VarC".to_string(),
"VarD".to_string(),
],
values_as_flags: false,
}),
);
assert_eq!(
export_type[1].name,
EnumComponent::type_path().to_string() + "::VarB"
);
assert_eq!(
export_type[1].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "0".to_string(),
property_type: None,
type_field: FieldType::Int,
value: serde_json::Value::default(),
}],
})
);
assert_eq!(
export_type[2].name,
EnumComponent::type_path().to_string() + "::VarC"
);
assert_eq!(
export_type[2].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "0".to_string(),
property_type: Some(TestStruct::type_path().to_string()),
type_field: FieldType::Class,
value: serde_json::Value::default(),
}],
})
);
assert_eq!(
export_type[3].name,
EnumComponent::type_path().to_string() + "::VarD"
);
assert_eq!(
export_type[3].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "0".to_string(),
property_type: Some("(u16, f32)".to_string()),
type_field: FieldType::Class,
value: serde_json::Value::default(),
}],
})
);
assert_eq!(export_type[4].name, EnumComponent::type_path().to_string());
assert_eq!(
export_type[4].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![
Member {
name: ":variant".to_string(),
property_type: Some(EnumComponent::type_path().to_string() + ":::Variant"),
type_field: FieldType::String,
value: serde_json::json!("VarA"),
},
Member {
name: "VarB".to_string(),
property_type: Some(EnumComponent::type_path().to_string() + "::VarB"),
type_field: FieldType::Class,
value: serde_json::Value::default(),
},
Member {
name: "VarC".to_string(),
property_type: Some(EnumComponent::type_path().to_string() + "::VarC"),
type_field: FieldType::Class,
value: serde_json::Value::default(),
},
Member {
name: "VarD".to_string(),
property_type: Some(EnumComponent::type_path().to_string() + "::VarD"),
type_field: FieldType::Class,
value: serde_json::Value::default(),
},
],
})
);
}
#[test]
fn exports_dependencies_before_dependents() {
#[derive(Component, Reflect)]
#[reflect(Component)]
struct Dependency;
#[derive(Component, Reflect)]
#[reflect(Component)]
struct Dependent {
dependency: Dependency,
}
let mut registry = TypeRegistry::new();
registry.register::<Dependency>();
registry.register::<Dependent>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All).to_vec();
let exported_names = exports
.iter()
.map(|export| export.name.clone())
.collect::<Vec<_>>();
assert_eq!(
exported_names,
vec![
Dependency::type_path().to_string(),
Dependent::type_path().to_string(),
]
);
}
#[test]
fn generate_list_properties() {
#[derive(Reflect, Default)]
#[reflect(Default)]
struct TestTuple(f32);
#[derive(Reflect, Default, Eq, PartialEq, Hash)]
#[reflect(Default)]
enum TestEnum {
VarA,
#[default]
VarB,
VarC,
}
#[derive(Reflect, Default, Component)]
#[reflect(Default, Component)]
struct MyComponent {
a_list: Vec<TestTuple>,
a_set: HashSet<TestEnum>,
a_map: HashMap<TestEnum, TestTuple>,
}
let mut registry = TypeRegistry::new();
registry.register::<TestEnum>();
registry.register::<TestTuple>();
registry.register::<MyComponent>();
let exports = TypeExportRegistry::from_registry(®istry, &TiledFilter::All);
let export_type = &exports.types.get(Vec::<TestTuple>::type_path()).unwrap();
assert_eq!(export_type.length(), 1);
assert_eq!(
export_type[0].name,
Vec::<TestTuple>::type_path().to_string()
);
assert_eq!(
export_type[0].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "list".to_string(),
property_type: None,
type_field: FieldType::List,
value: serde_json::json!([
{
"propertyType": serde_json::Value::String(TestTuple::type_path().to_string()),
"type": "class",
"value": {
"0": 0.0
}
}
])
}]
})
);
let export_type = &exports.types.get(HashSet::<TestEnum>::type_path()).unwrap();
dbg!(&export_type);
assert_eq!(export_type.length(), 1);
assert_eq!(
export_type[0].name,
HashSet::<TestEnum>::type_path().to_string()
);
assert_eq!(
export_type[0].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "set".to_string(),
property_type: None,
type_field: FieldType::List,
value: serde_json::json!([
{
"propertyType": serde_json::Value::String(TestEnum::type_path().to_string()),
"type": "class",
"value": {}
}
])
}]
})
);
let export_type = &exports
.types
.get(HashMap::<TestEnum, TestTuple>::type_path())
.unwrap();
assert_eq!(export_type.length(), 2);
assert!(export_type[0].id > export_type[1].id);
assert_eq!(
export_type[0].name,
HashMap::<TestEnum, TestTuple>::type_path().to_string()
);
assert_eq!(
export_type[0].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![Member {
name: "map".to_string(),
property_type: None,
type_field: FieldType::List,
value: serde_json::json!([
{
"propertyType": serde_json::Value::String(HashMap::<TestEnum, TestTuple>::type_path().to_string() + ":::MapItem"),
"type": "class",
"value": serde_json::Value::Null
}
])
}]
})
);
assert_eq!(
export_type[1].name,
HashMap::<TestEnum, TestTuple>::type_path().to_string() + ":::MapItem"
);
assert_eq!(
export_type[1].type_data,
TypeData::Class(Class {
use_as: USE_AS_PROPERTY.to_vec(),
color: DEFAULT_COLOR.to_string(),
draw_fill: true,
members: vec![
Member {
name: "key".to_string(),
property_type: Some(TestEnum::type_path().to_string()),
type_field: FieldType::Class,
value: serde_json::json!({})
},
Member {
name: "value".to_string(),
property_type: Some(TestTuple::type_path().to_string()),
type_field: FieldType::Class,
value: serde_json::json!({
"0": 0.0
})
}
]
})
);
}
}