mod builtin;
use std::collections::{BTreeMap, HashMap};
use std::fmt::{Debug, Display, Formatter};
use std::path::{Path, PathBuf};
use bevy_ecs::entity::Entity;
use bevy_ecs::prelude::{Resource, World};
use serde_json::{json, Value};
use uuid::Uuid;
use crate::assets::{AssetError, AssetServer, Handle, MeshAsset, TextureAsset};
use crate::runtime::SceneId;
pub trait Reflect: 'static {
fn type_info() -> TypeInfo;
}
#[derive(Clone, Debug, PartialEq)]
pub enum TypeInfo {
Bool,
Int {
name: &'static str,
min: i128,
max: i128,
},
Float,
String,
Path,
Array(Box<TypeInfo>, usize),
List(Box<TypeInfo>),
Map(Box<TypeInfo>),
Option(Box<TypeInfo>),
Struct(StructInfo),
Enum(EnumInfo),
Entity,
Handle(AssetKind),
}
#[derive(Clone, Debug, PartialEq)]
pub struct StructInfo {
pub name: &'static str,
pub fields: Vec<FieldInfo>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FieldInfo {
pub name: &'static str,
pub ty: TypeInfo,
pub hints: Hints,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Hints {
pub unit: &'static str,
pub min: Option<f64>,
pub max: Option<f64>,
pub doc: &'static str,
pub color: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EnumInfo {
pub name: &'static str,
pub variants: Vec<VariantInfo>,
}
impl EnumInfo {
#[must_use]
pub fn variant(&self, name: &str) -> Option<&VariantInfo> {
self.variants.iter().find(|variant| variant.name == name)
}
#[must_use]
pub fn is_unit_only(&self) -> bool {
self.variants
.iter()
.all(|variant| variant.fields == VariantFields::Unit)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct VariantInfo {
pub name: &'static str,
pub fields: VariantFields,
}
#[derive(Clone, Debug, PartialEq)]
pub enum VariantFields {
Unit,
Tuple(Vec<TypeInfo>),
Struct(Vec<FieldInfo>),
}
#[derive(Clone, Copy)]
pub struct AssetKind {
pub name: &'static str,
path: fn(&AssetServer, u64) -> Option<PathBuf>,
load: fn(&mut AssetServer, &Path) -> Result<u64, AssetError>,
paths: fn(&AssetServer) -> Vec<PathBuf>,
}
impl AssetKind {
pub fn load(
&self,
assets: &mut AssetServer,
path: &Path,
) -> Result<u64, AssetError> {
(self.load)(assets, path)
}
#[must_use]
pub fn loaded_paths(&self, assets: &AssetServer) -> Vec<PathBuf> {
let mut paths = (self.paths)(assets);
paths.sort();
paths
}
}
impl Debug for AssetKind {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.name)
}
}
impl PartialEq for AssetKind {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
pub trait ReflectAsset: Sized + 'static {
const KIND: AssetKind;
}
fn texture_path(assets: &AssetServer, key: u64) -> Option<PathBuf> {
assets
.textures
.path(Handle::from_key(key))
.map(Path::to_path_buf)
}
fn load_texture(
assets: &mut AssetServer,
path: &Path,
) -> Result<u64, AssetError> {
assets.load_texture(path).map(Handle::key)
}
fn mesh_path(assets: &AssetServer, key: u64) -> Option<PathBuf> {
assets
.meshes
.path(Handle::from_key(key))
.map(Path::to_path_buf)
}
fn load_mesh(assets: &mut AssetServer, path: &Path) -> Result<u64, AssetError> {
assets.load_mesh(path).map(Handle::key)
}
fn texture_paths(assets: &AssetServer) -> Vec<PathBuf> {
assets
.textures
.paths()
.map(|(_, path)| path.to_path_buf())
.collect()
}
fn mesh_paths(assets: &AssetServer) -> Vec<PathBuf> {
assets
.meshes
.paths()
.map(|(_, path)| path.to_path_buf())
.collect()
}
impl ReflectAsset for TextureAsset {
const KIND: AssetKind = AssetKind {
name: "texture",
path: texture_path,
load: load_texture,
paths: texture_paths,
};
}
impl ReflectAsset for MeshAsset {
const KIND: AssetKind = AssetKind {
name: "mesh",
path: mesh_path,
load: load_mesh,
paths: mesh_paths,
};
}
impl<T: ReflectAsset> Reflect for Handle<T> {
fn type_info() -> TypeInfo {
TypeInfo::Handle(T::KIND)
}
}
impl Reflect for Entity {
fn type_info() -> TypeInfo {
TypeInfo::Entity
}
}
macro_rules! reflect_leaf {
($($ty:ty => $info:expr),* $(,)?) => {
$(impl Reflect for $ty {
fn type_info() -> TypeInfo {
$info
}
})*
};
}
macro_rules! reflect_int {
($($ty:ty),*) => {
$(impl Reflect for $ty {
fn type_info() -> TypeInfo {
TypeInfo::Int {
name: stringify!($ty),
min: <$ty>::MIN as i128,
max: <$ty>::MAX as i128,
}
}
})*
};
}
reflect_leaf! {
bool => TypeInfo::Bool,
f32 => TypeInfo::Float,
f64 => TypeInfo::Float,
String => TypeInfo::String,
PathBuf => TypeInfo::Path,
}
reflect_int!(i8, i16, i32, i64, u8, u16, u32, u64, isize, usize);
impl<T: Reflect, const N: usize> Reflect for [T; N] {
fn type_info() -> TypeInfo {
TypeInfo::Array(Box::new(T::type_info()), N)
}
}
impl<T: Reflect> Reflect for Vec<T> {
fn type_info() -> TypeInfo {
TypeInfo::List(Box::new(T::type_info()))
}
}
impl<T: Reflect> Reflect for Option<T> {
fn type_info() -> TypeInfo {
TypeInfo::Option(Box::new(T::type_info()))
}
}
impl<T: Reflect> Reflect for BTreeMap<String, T> {
fn type_info() -> TypeInfo {
TypeInfo::Map(Box::new(T::type_info()))
}
}
impl<T: Reflect, S: 'static> Reflect for HashMap<String, T, S> {
fn type_info() -> TypeInfo {
TypeInfo::Map(Box::new(T::type_info()))
}
}
pub trait IntoHint<T> {
fn into_hint(self) -> T;
}
impl IntoHint<&'static str> for &'static str {
fn into_hint(self) -> &'static str {
self
}
}
impl IntoHint<Option<f64>> for f64 {
fn into_hint(self) -> Option<f64> {
Some(self)
}
}
impl IntoHint<bool> for bool {
fn into_hint(self) -> bool {
self
}
}
#[macro_export]
macro_rules! reflect {
(struct $name:ident { $($body:tt)* }) => {
impl $crate::reflect::Reflect for $name {
fn type_info() -> $crate::reflect::TypeInfo {
$crate::reflect::TypeInfo::Struct($crate::reflect::StructInfo {
name: ::std::stringify!($name),
fields: $crate::reflect!(@fields $($body)*),
})
}
}
const _: () = {
#[allow(dead_code)]
fn check(value: &$name) {
$crate::reflect!(@check_fields [$name] value; $($body)*);
}
};
};
(struct $name:ident ( $inner:ty )) => {
impl $crate::reflect::Reflect for $name {
fn type_info() -> $crate::reflect::TypeInfo {
<$inner as $crate::reflect::Reflect>::type_info()
}
}
const _: () = {
#[allow(dead_code)]
fn check() {
let _: fn($inner) -> $name = $name;
}
};
};
(enum $name:ident {
$( $variant:ident $( ( $($tuple:ty),* $(,)? ) )? $( { $($body:tt)* } )? ),* $(,)?
}) => {
impl $crate::reflect::Reflect for $name {
fn type_info() -> $crate::reflect::TypeInfo {
$crate::reflect::TypeInfo::Enum($crate::reflect::EnumInfo {
name: ::std::stringify!($name),
variants: ::std::vec![$(
$crate::reflect::VariantInfo {
name: ::std::stringify!($variant),
fields: $crate::reflect!(
@variant [$( $($tuple),* )?] [$( $($body)* )?]
),
}
),*],
})
}
}
const _: () = {
#[allow(dead_code)]
fn check(value: &$name) {
match value {
$( $name::$variant { .. } => {} )*
}
$( $crate::reflect!(
@check_variant $name, $variant, value,
[$( $($tuple),* )?] [$( $($body)* )?]
); )*
}
};
};
(@fields $(
$(#[$attr:ident])? $field:ident : $ty:ty
$({ $($hint:ident : $value:expr),* $(,)? })?
),* $(,)?) => {{
#[allow(unused_mut)]
let mut fields = ::std::vec::Vec::new();
$( $crate::reflect!(
@field fields, [$($attr)?], $field, $ty, { $($($hint : $value),*)? }
); )*
fields
}};
(@field $fields:ident, [skip], $field:ident, $ty:ty,
{ $($hint:ident : $value:expr),* }) => {};
(@field $fields:ident, [], $field:ident, $ty:ty,
{ $($hint:ident : $value:expr),* }) => {
$fields.push($crate::reflect::FieldInfo {
name: ::std::stringify!($field),
ty: <$ty as $crate::reflect::Reflect>::type_info(),
hints: {
#[allow(unused_mut)]
let mut hints = $crate::reflect::Hints::default();
$( hints.$hint = $crate::reflect::IntoHint::into_hint($value); )*
hints
},
});
};
(@variant [] []) => { $crate::reflect::VariantFields::Unit };
(@variant [$($tuple:ty),+] []) => {
$crate::reflect::VariantFields::Tuple(::std::vec![
$( <$tuple as $crate::reflect::Reflect>::type_info() ),+
])
};
(@variant [] [$($body:tt)+]) => {
$crate::reflect::VariantFields::Struct($crate::reflect!(@fields $($body)+))
};
(@check_variant $name:ident, $variant:ident, $value:ident, [] []) => {};
(@check_variant $name:ident, $variant:ident, $value:ident,
[$($tuple:ty),+] []) => {
let _: fn($($tuple),+) -> $name = $name::$variant;
};
(@check_variant $name:ident, $variant:ident, $value:ident,
[] [$($body:tt)+]) => {
$crate::reflect!(@check_fields [$name::$variant] $value; $($body)+);
};
(@check_fields [$($path:tt)+] $value:ident; $(
$(#[$attr:ident])? $field:ident : $ty:ty
$({ $($hint:ident : $hint_value:expr),* $(,)? })?
),* $(,)?) => {
#[allow(irrefutable_let_patterns)]
if let $($path)+ { $($field),* } = $value {
$( let _: &$ty = $field; )*
}
};
}
pub const VERSION_KEY: &str = "$version";
pub const ASSET_KEY: &str = "$asset";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FieldMigration {
Rename { from: String, to: String },
Remove(String),
}
impl FieldMigration {
#[must_use]
pub fn rename(from: impl Into<String>, to: impl Into<String>) -> Self {
Self::Rename {
from: from.into(),
to: to.into(),
}
}
#[must_use]
pub fn remove(field: impl Into<String>) -> Self {
Self::Remove(field.into())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReflectProblem {
UnknownField,
UnknownVariant(String),
NewerVersion,
UnsavedAsset,
Asset(String),
WrongKind(&'static str),
NoSuchPath,
NotAStruct,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ReflectError {
pub component: String,
pub saved_version: u32,
pub current_version: u32,
pub path: String,
pub problem: ReflectProblem,
}
impl Display for ReflectError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let path = if self.path.is_empty() {
"/"
} else {
&self.path
};
write!(
formatter,
"component `{}` at `{path}` (saved version {}, current {}): ",
self.component, self.saved_version, self.current_version
)?;
match &self.problem {
ReflectProblem::UnknownField => formatter.write_str(
"the type has no such field; register a rename or remove \
migration instead of dropping the saved value",
),
ReflectProblem::UnknownVariant(name) => {
write!(formatter, "the type has no variant `{name}`")
}
ReflectProblem::NewerVersion => formatter
.write_str("saved by a newer build with more migrations"),
ReflectProblem::UnsavedAsset => formatter.write_str(
"references an asset that was not loaded from a file",
),
ReflectProblem::Asset(message) => {
write!(formatter, "asset failed to load: {message}")
}
ReflectProblem::WrongKind(expected) => {
write!(formatter, "expected {expected}")
}
ReflectProblem::NoSuchPath => {
formatter.write_str("the type has nothing at this path")
}
ReflectProblem::NotAStruct => formatter
.write_str("migrations need a component saved as a struct"),
}
}
}
impl std::error::Error for ReflectError {}
pub type Located = (String, ReflectProblem);
impl TypeInfo {
#[must_use]
pub fn has_references(&self) -> bool {
match self {
Self::Entity | Self::Handle(_) => true,
Self::Array(item, _)
| Self::List(item)
| Self::Map(item)
| Self::Option(item) => item.has_references(),
Self::Struct(info) => {
info.fields.iter().any(|field| field.ty.has_references())
}
Self::Enum(info) => {
info.variants.iter().any(|variant| match &variant.fields {
VariantFields::Unit => false,
VariantFields::Tuple(items) => {
items.iter().any(Self::has_references)
}
VariantFields::Struct(fields) => {
fields.iter().any(|field| field.ty.has_references())
}
})
}
_ => false,
}
}
#[must_use]
pub fn zero_value(&self) -> Value {
match self {
Self::Bool => Value::Bool(false),
Self::Int { .. } => json!(0),
Self::Float => json!(0.0),
Self::String | Self::Path => json!(""),
Self::Array(item, len) => {
Value::Array(vec![item.zero_value(); *len])
}
Self::List(_) => json!([]),
Self::Map(_) => json!({}),
Self::Option(_) | Self::Entity | Self::Handle(_) => Value::Null,
Self::Struct(info) => fields_zero(&info.fields),
Self::Enum(info) => {
info.variants.first().map_or(Value::Null, variant_zero)
}
}
}
#[must_use]
pub fn kind_name(&self) -> &'static str {
match self {
Self::Bool => "a boolean",
Self::Int { .. } => "an integer",
Self::Float => "a number",
Self::String => "a string",
Self::Path => "a path string",
Self::Array(..) => "an array of fixed length",
Self::List(_) => "an array",
Self::Map(_) => "an object",
Self::Option(_) => "null or a value",
Self::Struct(_) => "an object with the type's fields",
Self::Enum(_) => "a variant name or {\"Variant\": payload}",
Self::Entity => "an object ID or null",
Self::Handle(_) => "{\"$asset\": path}",
}
}
#[must_use]
pub fn accepts(&self, value: &Value) -> bool {
match self {
Self::Bool => value.is_boolean(),
Self::Int { .. } => value.is_i64() || value.is_u64(),
Self::Float => value.is_number(),
Self::String | Self::Path => value.is_string(),
Self::Array(_, len) => {
value.as_array().is_some_and(|items| items.len() == *len)
}
Self::List(_) => value.is_array(),
Self::Map(_) | Self::Struct(_) => value.is_object(),
Self::Option(inner) => value.is_null() || inner.accepts(value),
Self::Enum(_) => value.is_string() || value.is_object(),
Self::Entity => {
value.is_null()
|| value
.as_str()
.is_some_and(|text| text.parse::<Uuid>().is_ok())
}
Self::Handle(_) => {
value.get(ASSET_KEY).is_some_and(Value::is_string)
}
}
}
#[must_use]
pub fn at(&self, pointer: &str) -> Option<&TypeInfo> {
if pointer.is_empty() {
return Some(self);
}
if let Self::Option(inner) = self {
return inner.at(pointer);
}
let (segment, rest) = split_first(pointer)?;
match self {
Self::Struct(info) => field_type(&info.fields, &segment)?.at(rest),
Self::Map(item) => item.at(rest),
Self::List(item) => {
segment.parse::<usize>().ok()?;
item.at(rest)
}
Self::Array(item, len) => {
(segment.parse::<usize>().ok()? < *len).then_some(())?;
item.at(rest)
}
Self::Enum(info) => match &info.variant(&segment)?.fields {
VariantFields::Unit => None,
VariantFields::Tuple(items) if items.len() == 1 => {
items[0].at(rest)
}
VariantFields::Tuple(items) => {
let (index, rest) = split_first(rest)?;
items.get(index.parse::<usize>().ok()?)?.at(rest)
}
VariantFields::Struct(fields) => {
let (name, rest) = split_first(rest)?;
field_type(fields, &name)?.at(rest)
}
},
_ => None,
}
}
}
fn split_first(pointer: &str) -> Option<(String, &str)> {
let rest = pointer.strip_prefix('/')?;
let (segment, rest) = match rest.find('/') {
Some(end) => (&rest[..end], &rest[end..]),
None => (rest, ""),
};
Some((segment.replace("~1", "/").replace("~0", "~"), rest))
}
fn field_type<'a>(fields: &'a [FieldInfo], name: &str) -> Option<&'a TypeInfo> {
fields
.iter()
.find(|field| field.name == name)
.map(|field| &field.ty)
}
fn fields_zero(fields: &[FieldInfo]) -> Value {
Value::Object(
fields
.iter()
.map(|field| (field.name.to_owned(), field.ty.zero_value()))
.collect(),
)
}
#[must_use]
pub fn variant_zero(variant: &VariantInfo) -> Value {
let payload = match &variant.fields {
VariantFields::Unit => return json!(variant.name),
VariantFields::Tuple(items) if items.len() == 1 => {
items[0].zero_value()
}
VariantFields::Tuple(items) => {
Value::Array(items.iter().map(TypeInfo::zero_value).collect())
}
VariantFields::Struct(fields) => fields_zero(fields),
};
json!({ variant.name: payload })
}
fn escape(segment: &str) -> String {
segment.replace('~', "~0").replace('/', "~1")
}
pub fn walk(
info: &TypeInfo,
value: &mut Value,
visit: &mut dyn FnMut(&TypeInfo, &mut Value) -> Result<(), ReflectProblem>,
) -> Result<(), Located> {
walk_at(info, value, &mut String::new(), visit)
}
fn walk_at(
info: &TypeInfo,
value: &mut Value,
pointer: &mut String,
visit: &mut dyn FnMut(&TypeInfo, &mut Value) -> Result<(), ReflectProblem>,
) -> Result<(), Located> {
let located = |pointer: &String, problem| (pointer.clone(), problem);
match (info, value) {
(TypeInfo::Entity, value) => {
visit(info, value).map_err(|problem| located(pointer, problem))
}
(TypeInfo::Handle(_), value) if !value.is_null() => {
visit(info, value).map_err(|problem| located(pointer, problem))
}
(TypeInfo::Option(inner), value) if !value.is_null() => {
walk_at(inner, value, pointer, visit)?;
if **inner == TypeInfo::Entity && *value == placeholder() {
*value = Value::Null;
}
Ok(())
}
(TypeInfo::Struct(info), Value::Object(map)) => {
walk_fields(&info.fields, map, pointer, visit)
}
(
TypeInfo::Array(item, _) | TypeInfo::List(item),
Value::Array(items),
) => {
for (index, value) in items.iter_mut().enumerate() {
let length = pointer.len();
pointer.push_str(&format!("/{index}"));
walk_at(item, value, pointer, visit)?;
pointer.truncate(length);
}
Ok(())
}
(TypeInfo::Map(item), Value::Object(map)) => {
for (key, value) in map.iter_mut() {
let length = pointer.len();
pointer.push('/');
pointer.push_str(&escape(key));
walk_at(item, value, pointer, visit)?;
pointer.truncate(length);
}
Ok(())
}
(TypeInfo::Enum(info), Value::String(name)) => {
if info.variant(name).is_some() {
Ok(())
} else {
Err(located(
pointer,
ReflectProblem::UnknownVariant(name.clone()),
))
}
}
(TypeInfo::Enum(info), Value::Object(map)) if map.len() == 1 => {
let (name, payload) = map.iter_mut().next().expect("one entry");
let Some(variant) = info.variant(name) else {
return Err(located(
pointer,
ReflectProblem::UnknownVariant(name.clone()),
));
};
let length = pointer.len();
pointer.push('/');
pointer.push_str(&escape(name));
match (&variant.fields, payload) {
(VariantFields::Struct(fields), Value::Object(map)) => {
walk_fields(fields, map, pointer, visit)?;
}
(VariantFields::Tuple(items), payload) if items.len() == 1 => {
walk_at(&items[0], payload, pointer, visit)?;
}
(VariantFields::Tuple(items), Value::Array(values)) => {
for (index, (item, value)) in
items.iter().zip(values.iter_mut()).enumerate()
{
let length = pointer.len();
pointer.push_str(&format!("/{index}"));
walk_at(item, value, pointer, visit)?;
pointer.truncate(length);
}
}
_ => {}
}
pointer.truncate(length);
Ok(())
}
_ => Ok(()),
}
}
fn walk_fields(
fields: &[FieldInfo],
map: &mut serde_json::Map<String, Value>,
pointer: &mut String,
visit: &mut dyn FnMut(&TypeInfo, &mut Value) -> Result<(), ReflectProblem>,
) -> Result<(), Located> {
for (key, value) in map.iter_mut() {
let length = pointer.len();
pointer.push('/');
pointer.push_str(&escape(key));
let Some(field) = fields.iter().find(|field| field.name == key) else {
return Err((pointer.clone(), ReflectProblem::UnknownField));
};
walk_at(&field.ty, value, pointer, visit)?;
pointer.truncate(length);
}
Ok(())
}
pub fn take_version(value: &mut Value) -> Result<u32, Located> {
match value
.as_object_mut()
.and_then(|map| map.remove(VERSION_KEY))
{
Some(version) => version
.as_u64()
.and_then(|version| u32::try_from(version).ok())
.ok_or_else(|| {
(
format!("/{}", escape(VERSION_KEY)),
ReflectProblem::WrongKind("a version number"),
)
}),
None => Ok(0),
}
}
pub fn migrate(
value: &mut Value,
saved: u32,
migrations: &[FieldMigration],
) -> Result<(), Located> {
let pending = migrations
.get(saved as usize..)
.ok_or((String::new(), ReflectProblem::NewerVersion))?;
if let Value::Object(map) = value {
for migration in pending {
match migration {
FieldMigration::Rename { from, to } => {
if let Some(moved) = map.remove(from) {
map.insert(to.clone(), moved);
}
}
FieldMigration::Remove(field) => {
map.remove(field);
}
}
}
}
Ok(())
}
fn placeholder() -> Value {
Entity::PLACEHOLDER.to_bits().into()
}
pub fn to_scene_form(
info: &TypeInfo,
value: &mut Value,
world: &World,
) -> Result<(), Located> {
walk(info, value, &mut |info, value| match info {
TypeInfo::Entity => {
*value = value
.as_u64()
.and_then(Entity::try_from_bits)
.and_then(|entity| world.get::<SceneId>(entity))
.map_or(Value::Null, |id| Value::String(id.0.to_string()));
Ok(())
}
TypeInfo::Handle(kind) => {
let key = value
.as_u64()
.ok_or(ReflectProblem::WrongKind("a handle key"))?;
let assets = world
.get_resource::<AssetServer>()
.ok_or(ReflectProblem::UnsavedAsset)?;
let path =
(kind.path)(assets, key).ok_or(ReflectProblem::UnsavedAsset)?;
*value = json!({ ASSET_KEY: path });
Ok(())
}
_ => Ok(()),
})
}
pub fn from_scene_form(
info: &TypeInfo,
value: &mut Value,
world: &mut World,
ids: Option<&HashMap<Uuid, Entity>>,
) -> Result<(), Located> {
let mut searched: Option<HashMap<Uuid, Entity>> = None;
walk(info, value, &mut |info, value| match info {
TypeInfo::Entity => {
if value.is_null() {
*value = placeholder();
return Ok(());
}
let id = value
.as_str()
.and_then(|text| text.parse::<Uuid>().ok())
.ok_or(ReflectProblem::WrongKind("an object ID"))?;
let ids = match ids {
Some(ids) => ids,
None => searched.get_or_insert_with(|| {
let mut query = world.query::<(Entity, &SceneId)>();
query
.iter(world)
.map(|(entity, id)| (id.0, entity))
.collect()
}),
};
let entity = ids.get(&id).copied().unwrap_or(Entity::PLACEHOLDER);
*value = entity.to_bits().into();
Ok(())
}
TypeInfo::Handle(kind) => {
let path = value
.get(ASSET_KEY)
.and_then(Value::as_str)
.ok_or(ReflectProblem::WrongKind("{\"$asset\": path}"))?;
let mut assets = world
.get_resource_mut::<AssetServer>()
.ok_or(ReflectProblem::Asset("no AssetServer".into()))?;
let key = kind
.load(&mut assets, Path::new(path))
.map_err(|error| ReflectProblem::Asset(error.to_string()))?;
*value = key.into();
Ok(())
}
_ => Ok(()),
})
}
pub fn map_asset_paths<E>(
value: &mut Value,
convert: &mut impl FnMut(&Path) -> Result<PathBuf, E>,
) -> Result<(), E> {
match value {
Value::Object(map) => {
if map.len() == 1 {
if let Some(Value::String(path)) = map.get_mut(ASSET_KEY) {
let converted = convert(Path::new(path.as_str()))?;
*path = converted.to_string_lossy().into_owned();
return Ok(());
}
}
map.values_mut()
.try_for_each(|value| map_asset_paths(value, convert))
}
Value::Array(items) => items
.iter_mut()
.try_for_each(|value| map_asset_paths(value, convert)),
_ => Ok(()),
}
}
pub struct TypeRegistry {
resources: BTreeMap<String, TypeInfo>,
assets: BTreeMap<String, TypeInfo>,
}
impl Default for TypeRegistry {
fn default() -> Self {
let mut registry = Self {
resources: BTreeMap::new(),
assets: BTreeMap::new(),
};
builtin::register(&mut registry);
registry
}
}
impl TypeRegistry {
pub fn register_resource<T: Resource + Reflect>(
&mut self,
name: impl Into<String>,
) {
self.resources.insert(name.into(), T::type_info());
}
pub fn register_asset<T: Reflect>(&mut self, name: impl Into<String>) {
self.assets.insert(name.into(), T::type_info());
}
pub fn resources(&self) -> impl Iterator<Item = (&str, &TypeInfo)> {
self.resources
.iter()
.map(|(name, info)| (name.as_str(), info))
}
pub fn assets(&self) -> impl Iterator<Item = (&str, &TypeInfo)> {
self.assets.iter().map(|(name, info)| (name.as_str(), info))
}
}
#[cfg(test)]
mod tests;