mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
Improving error reporting. (#853)
This commit is contained in:
@@ -207,15 +207,19 @@ fn surface_data_info(resource_manager: &ResourceManager, data: &SurfaceResource)
|
||||
.resource_path(data.as_ref())
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "External".to_string());
|
||||
let guard = data.data_ref();
|
||||
format!(
|
||||
"{}\nVertices: {}\nTriangles: {}\nUse Count: {}\nId: {}",
|
||||
kind,
|
||||
guard.vertex_buffer.vertex_count(),
|
||||
guard.geometry_buffer.len(),
|
||||
use_count,
|
||||
id
|
||||
)
|
||||
if data.is_ok() {
|
||||
let guard = data.data_ref();
|
||||
format!(
|
||||
"{}\nVertices: {}\nTriangles: {}\nUse Count: {}\nId: {}",
|
||||
kind,
|
||||
guard.vertex_buffer.vertex_count(),
|
||||
guard.geometry_buffer.len(),
|
||||
use_count,
|
||||
id
|
||||
)
|
||||
} else {
|
||||
format!("{}\nNot loaded\nUse Count: {}\nId: {}", kind, use_count, id)
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfaceDataPropertyEditor {
|
||||
|
||||
@@ -27,6 +27,8 @@ pub enum FileError {
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl std::error::Error for FileError {}
|
||||
|
||||
impl Display for FileError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
||||
@@ -64,7 +64,6 @@ const INVALID_GENERATION: u32 = 0;
|
||||
/// block. It allows to create and delete objects much faster than if they'll
|
||||
/// be allocated on heap. Also since objects stored in contiguous memory block
|
||||
/// they can be effectively accessed because such memory layout is cache-friendly.
|
||||
#[derive(Debug)]
|
||||
pub struct Pool<T, P = Option<T>>
|
||||
where
|
||||
T: Sized,
|
||||
@@ -74,6 +73,16 @@ where
|
||||
free_stack: Vec<u32>,
|
||||
}
|
||||
|
||||
impl<T: Sized + Debug, P: PayloadContainer<Element = T> + 'static> Debug for Pool<T, P> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut s = f.debug_struct("Pool");
|
||||
for (handle, value) in self.pair_iter() {
|
||||
s.field(&handle.to_string(), value);
|
||||
}
|
||||
s.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait unifies pool objects and their variants.
|
||||
///
|
||||
/// If T is the pool object type, [`ObjectOrVariant::convert_to_dest_type`] returns the pool object itself.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
//! Possible errors that may occur during serialization/deserialization.
|
||||
|
||||
use crate::io::FileError;
|
||||
use crate::visitor::Visitor;
|
||||
use base64::DecodeError;
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
use std::{
|
||||
@@ -32,6 +33,9 @@ use std::{
|
||||
/// Errors that may occur while reading or writing [`crate::visitor::Visitor`].
|
||||
#[derive(Debug)]
|
||||
pub enum VisitError {
|
||||
/// An error that occured for multiple reasons, when there are multiple potential ways
|
||||
/// to visit a node, and all of them lead to errors.
|
||||
Multiple(Vec<VisitError>),
|
||||
/// An [std::io::Error] occured while reading or writing a file with Visitor data.
|
||||
Io(std::io::Error),
|
||||
/// When a field is encoded as bytes, the field data is prefixed by an identifying byte
|
||||
@@ -72,7 +76,12 @@ pub enum VisitError {
|
||||
/// to a single shared value. This causes the visitor to store the data once and then later references
|
||||
/// to the same value point back to its first occurrence. This error occurs if one of these references
|
||||
/// points to a value of the wrong type.
|
||||
TypeMismatch,
|
||||
TypeMismatch {
|
||||
/// The type that was visiting when the error occurred.
|
||||
expected: &'static str,
|
||||
/// The type that was stored in the `Rc` or `Arc`.
|
||||
actual: &'static str,
|
||||
},
|
||||
/// Attempting to visit a mutably borrowed RefCell.
|
||||
RefCellAlreadyMutableBorrowed,
|
||||
/// A plain-text error message that could indicate almost anything.
|
||||
@@ -96,12 +105,45 @@ pub enum VisitError {
|
||||
|
||||
impl Error for VisitError {}
|
||||
|
||||
impl VisitError {
|
||||
/// Create a [`VisitError::FieldDoesNotExist`] containing the given field name and the
|
||||
/// breadcrumbs of the current visitor node.
|
||||
pub fn field_does_not_exist(name: &str, visitor: &Visitor) -> Self {
|
||||
Self::FieldDoesNotExist(visitor.breadcrumbs() + " > " + name)
|
||||
}
|
||||
/// Create an error from two errors.
|
||||
pub fn multiple(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Self::Multiple(mut a), Self::Multiple(mut b)) => {
|
||||
a.append(&mut b);
|
||||
Self::Multiple(a)
|
||||
}
|
||||
(Self::Multiple(mut a), b) => {
|
||||
a.push(b);
|
||||
Self::Multiple(a)
|
||||
}
|
||||
(a, Self::Multiple(mut b)) => {
|
||||
b.push(a);
|
||||
Self::Multiple(b)
|
||||
}
|
||||
(a, b) => Self::Multiple(vec![a, b]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for VisitError {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Multiple(errs) => {
|
||||
write!(f, "multiple errors:[")?;
|
||||
for err in errs {
|
||||
write!(f, "{err};")?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
Self::Io(io) => write!(f, "io error: {io}"),
|
||||
Self::UnknownFieldType(type_index) => write!(f, "unknown field type {type_index}"),
|
||||
Self::FieldDoesNotExist(name) => write!(f, "field does not exists {name}"),
|
||||
Self::FieldDoesNotExist(name) => write!(f, "field does not exist: {name}"),
|
||||
Self::FieldAlreadyExists(name) => write!(f, "field already exists {name}"),
|
||||
Self::RegionAlreadyExists(name) => write!(f, "region already exists {name}"),
|
||||
Self::InvalidCurrentNode => write!(f, "invalid current node"),
|
||||
@@ -113,7 +155,9 @@ impl Display for VisitError {
|
||||
Self::NoActiveNode => write!(f, "no active node"),
|
||||
Self::NotSupportedFormat => write!(f, "not supported format"),
|
||||
Self::InvalidName => write!(f, "invalid name"),
|
||||
Self::TypeMismatch => write!(f, "type mismatch"),
|
||||
Self::TypeMismatch { expected, actual } => {
|
||||
write!(f, "type mismatch. expected: {expected}, actual: {actual}")
|
||||
}
|
||||
Self::RefCellAlreadyMutableBorrowed => write!(f, "ref cell already mutable borrowed"),
|
||||
Self::User(msg) => write!(f, "user defined error: {msg}"),
|
||||
Self::UnexpectedRcNullIndex => write!(f, "unexpected rc null index"),
|
||||
|
||||
@@ -56,7 +56,7 @@ macro_rules! impl_visit_as_field {
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(VisitError::FieldDoesNotExist(name.to_owned()))
|
||||
Err(VisitError::field_does_not_exist(name, visitor))
|
||||
}
|
||||
} else if visitor.find_field(name).is_some() {
|
||||
Err(VisitError::FieldAlreadyExists(name.to_owned()))
|
||||
@@ -240,7 +240,7 @@ impl Visit for String {
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(VisitError::FieldDoesNotExist(name.to_owned()))
|
||||
Err(VisitError::field_does_not_exist(name, visitor))
|
||||
}
|
||||
} else if visitor.find_field(name).is_some() {
|
||||
Err(VisitError::FieldAlreadyExists(name.to_owned()))
|
||||
@@ -319,11 +319,23 @@ where
|
||||
if let Ok(res) = Rc::downcast::<T>(ptr.clone()) {
|
||||
*self = res;
|
||||
} else {
|
||||
return Err(VisitError::TypeMismatch);
|
||||
return Err(VisitError::TypeMismatch {
|
||||
expected: std::any::type_name::<T>(),
|
||||
actual: region.type_name_map.get(&id).unwrap_or(&"MISSING"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
region.type_name_map.insert(id, std::any::type_name::<T>());
|
||||
region.rc_map.insert(id, self.clone());
|
||||
unsafe { rc_to_raw(self).visit("RcData", &mut region)? };
|
||||
let result = unsafe { rc_to_raw(self).visit("RcData", &mut region) };
|
||||
// Sometimes visiting is done experimentally, just to see if it would succeed, and visiting continues along a different
|
||||
// path on failure. This means that the visitor must be in a valid state even after a failure, so we must remove
|
||||
// the invalid rc_map entry if visiting failed.
|
||||
if result.is_err() {
|
||||
region.type_name_map.remove(&id);
|
||||
region.rc_map.remove(&id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (mut id, serialize_data) = region.rc_id(self);
|
||||
@@ -410,11 +422,23 @@ where
|
||||
if let Ok(res) = Arc::downcast::<T>(ptr.clone()) {
|
||||
*self = res;
|
||||
} else {
|
||||
return Err(VisitError::TypeMismatch);
|
||||
return Err(VisitError::TypeMismatch {
|
||||
expected: std::any::type_name::<T>(),
|
||||
actual: region.type_name_map.get(&id).unwrap_or(&"MISSING"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
region.type_name_map.insert(id, std::any::type_name::<T>());
|
||||
region.arc_map.insert(id, self.clone());
|
||||
unsafe { arc_to_raw(self).visit("ArcData", &mut region)? };
|
||||
let result = unsafe { arc_to_raw(self).visit("ArcData", &mut region) };
|
||||
// Sometimes visiting is done experimentally, just to see if it would succeed, and visiting continues along a different
|
||||
// path on failure. This means that the visitor must be in a valid state even after a failure, so we must remove
|
||||
// the invalid arc_map entry if visiting failed.
|
||||
if result.is_err() {
|
||||
region.type_name_map.remove(&id);
|
||||
region.arc_map.remove(&id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let (mut id, serialize_data) = region.arc_id(self);
|
||||
@@ -444,14 +468,26 @@ where
|
||||
if let Ok(res) = Rc::downcast::<T>(ptr.clone()) {
|
||||
*self = Rc::downgrade(&res);
|
||||
} else {
|
||||
return Err(VisitError::TypeMismatch);
|
||||
return Err(VisitError::TypeMismatch {
|
||||
expected: std::any::type_name::<T>(),
|
||||
actual: region.type_name_map.get(&id).unwrap_or(&"MISSING"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Create new value wrapped into Rc and deserialize it.
|
||||
let rc = Rc::new(T::default());
|
||||
region.type_name_map.insert(id, std::any::type_name::<T>());
|
||||
region.rc_map.insert(id, rc.clone());
|
||||
|
||||
unsafe { rc_to_raw(&rc).visit("RcData", &mut region)? };
|
||||
let result = unsafe { rc_to_raw(&rc).visit("RcData", &mut region) };
|
||||
// Sometimes visiting is done experimentally, just to see if it would succeed, and visiting continues along a different
|
||||
// path on failure. This means that the visitor must be in a valid state even after a failure, so we must remove
|
||||
// the invalid rc_map entry if visiting failed.
|
||||
if result.is_err() {
|
||||
region.type_name_map.remove(&id);
|
||||
region.rc_map.remove(&id);
|
||||
return result;
|
||||
}
|
||||
|
||||
*self = Rc::downgrade(&rc);
|
||||
}
|
||||
@@ -487,12 +523,24 @@ where
|
||||
if let Ok(res) = Arc::downcast::<T>(ptr.clone()) {
|
||||
*self = Arc::downgrade(&res);
|
||||
} else {
|
||||
return Err(VisitError::TypeMismatch);
|
||||
return Err(VisitError::TypeMismatch {
|
||||
expected: std::any::type_name::<T>(),
|
||||
actual: region.type_name_map.get(&id).unwrap_or(&"MISSING"),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let arc = Arc::new(T::default());
|
||||
region.type_name_map.insert(id, std::any::type_name::<T>());
|
||||
region.arc_map.insert(id, arc.clone());
|
||||
unsafe { arc_to_raw(&arc).visit("ArcData", &mut region)? };
|
||||
let result = unsafe { arc_to_raw(&arc).visit("ArcData", &mut region) };
|
||||
// Sometimes visiting is done experimentally, just to see if it would succeed, and visiting continues along a different
|
||||
// path on failure. This means that the visitor must be in a valid state even after a failure, so we must remove
|
||||
// the invalid arc_map entry if visiting failed.
|
||||
if result.is_err() {
|
||||
region.type_name_map.remove(&id);
|
||||
region.arc_map.remove(&id);
|
||||
return result;
|
||||
}
|
||||
*self = Arc::downgrade(&arc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ where
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(VisitError::FieldDoesNotExist(name.to_owned()))
|
||||
Err(VisitError::field_does_not_exist(name, visitor))
|
||||
}
|
||||
} else if visitor.find_field(name).is_some() {
|
||||
Err(VisitError::FieldAlreadyExists(name.to_owned()))
|
||||
@@ -312,13 +312,25 @@ bitflags! {
|
||||
/// Whether the value of `x` gets written into `visitor` or overwritten with a value from `visitor` is determined
|
||||
/// by whether [Visitor::is_reading()] returns true or false.
|
||||
pub struct Visitor {
|
||||
/// The nodes that make up this visitors tree.
|
||||
nodes: Pool<VisitorNode>,
|
||||
/// The id for the next `Rc` or `Arc` that the visitor encounters.
|
||||
unique_id_counter: u64,
|
||||
/// The name of the type of the `Rc` or `Arc` associated with each id.
|
||||
/// This allows type mismatch errors to include type information.
|
||||
type_name_map: FxHashMap<u64, &'static str>,
|
||||
/// The `Rc` value associated with each id, to allow the visitor to find the `Rc` when reading.
|
||||
rc_map: FxHashMap<u64, Rc<dyn Any>>,
|
||||
/// The `Arc` value associated with each id, to allow the visitor to find the `Arc` when reading.
|
||||
arc_map: FxHashMap<u64, Arc<dyn Any + Send + Sync>>,
|
||||
/// True if this visitor is being read from, during loading.
|
||||
/// False if this visitor is being written to, during saving.
|
||||
reading: bool,
|
||||
/// The handle of the node that is currently being written to or read from.
|
||||
current_node: Handle<VisitorNode>,
|
||||
/// The handle of the start of the tree.
|
||||
root: Handle<VisitorNode>,
|
||||
/// The version number of the visitor. See [`VisitorVersion`].
|
||||
version: u32,
|
||||
/// A place to store whatever objects may be needed to help with reading and writing values.
|
||||
pub blackboard: Blackboard,
|
||||
@@ -524,6 +536,7 @@ impl Visitor {
|
||||
Self {
|
||||
nodes,
|
||||
unique_id_counter: 1,
|
||||
type_name_map: FxHashMap::default(),
|
||||
rc_map: FxHashMap::default(),
|
||||
arc_map: FxHashMap::default(),
|
||||
reading: false,
|
||||
@@ -555,6 +568,7 @@ impl Visitor {
|
||||
(id, false)
|
||||
} else {
|
||||
let id = self.gen_unique_id();
|
||||
self.type_name_map.insert(id, std::any::type_name::<T>());
|
||||
self.rc_map.insert(id, rc.clone());
|
||||
(id, true)
|
||||
}
|
||||
@@ -574,6 +588,7 @@ impl Visitor {
|
||||
(id, false)
|
||||
} else {
|
||||
let id = self.gen_unique_id();
|
||||
self.type_name_map.insert(id, std::any::type_name::<T>());
|
||||
self.arc_map.insert(id, arc.clone());
|
||||
(id, true)
|
||||
}
|
||||
@@ -633,7 +648,9 @@ impl Visitor {
|
||||
self.current_node = region;
|
||||
Ok(RegionGuard(self))
|
||||
} else {
|
||||
Err(VisitError::RegionDoesNotExist(self.build_breadcrumb(" > ")))
|
||||
Err(VisitError::RegionDoesNotExist(
|
||||
self.breadcrumbs() + " > " + name,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// Make sure that node does not exist already.
|
||||
@@ -655,6 +672,15 @@ impl Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a string representing all the regions from the root
|
||||
/// to the current node.
|
||||
pub fn breadcrumbs(&self) -> String {
|
||||
self.build_breadcrumb(" > ")
|
||||
}
|
||||
|
||||
/// Return a string representing all the regions from the root
|
||||
/// to the current node, using the given string as the separator between
|
||||
/// region names.
|
||||
fn build_breadcrumb(&self, separator: &str) -> String {
|
||||
let mut rev = String::new();
|
||||
let mut handle = self.current_node;
|
||||
@@ -689,6 +715,25 @@ impl Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the content of the current node in human-readable form.
|
||||
pub fn debug(&self) -> String {
|
||||
let mut w = Cursor::new(Vec::<u8>::new());
|
||||
let result = self.debug_to(&mut w);
|
||||
match result {
|
||||
Ok(()) => String::from_utf8_lossy(w.get_ref()).into_owned(),
|
||||
Err(err) => err.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the content of the current node in human-readable form.
|
||||
pub fn debug_to<W: Write>(&self, w: &mut W) -> VisitResult {
|
||||
let writer = AsciiWriter::default();
|
||||
writer.write_node(self, &self.nodes[self.current_node], 0, w)?;
|
||||
writeln!(w)?;
|
||||
w.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a string containing all the data of this Visitor in ascii form. The string is
|
||||
/// formatted to be human-readable with each node on its own line and tabs to indent child nodes.
|
||||
pub fn save_ascii_to_string(&self) -> String {
|
||||
|
||||
@@ -26,6 +26,17 @@ use crate::visitor::{
|
||||
Visit, VisitResult, Visitor,
|
||||
};
|
||||
|
||||
const POD_TYPES: &[&str] = &[
|
||||
"u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "f32", "f64",
|
||||
];
|
||||
|
||||
fn type_id_to_str(type_id: u8) -> &'static str {
|
||||
POD_TYPES
|
||||
.get(type_id as usize)
|
||||
.copied()
|
||||
.unwrap_or("Invalid Type")
|
||||
}
|
||||
|
||||
/// Trait for datatypes that can be converted directly into bytes.
|
||||
/// This is required for the type to be used in the Vec of a [PodVecView].
|
||||
pub trait Pod: Copy {
|
||||
@@ -139,7 +150,10 @@ impl<T: Pod> Visit for PodVecView<'_, T> {
|
||||
*self.vec = data;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(VisitError::TypeMismatch)
|
||||
Err(VisitError::TypeMismatch {
|
||||
expected: type_id_to_str(self.type_id),
|
||||
actual: type_id_to_str(*type_id),
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => Err(VisitError::FieldTypeDoesNotMatch {
|
||||
@@ -148,7 +162,7 @@ impl<T: Pod> Visit for PodVecView<'_, T> {
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Err(VisitError::FieldDoesNotExist(name.to_owned()))
|
||||
Err(VisitError::field_does_not_exist(name, visitor))
|
||||
}
|
||||
} else if visitor.find_field(name).is_some() {
|
||||
Err(VisitError::FieldAlreadyExists(name.to_owned()))
|
||||
|
||||
@@ -404,6 +404,7 @@ impl Reader for AsciiReader<'_> {
|
||||
let mut visitor = Visitor {
|
||||
nodes: Pool::new(),
|
||||
unique_id_counter: 1,
|
||||
type_name_map: Default::default(),
|
||||
rc_map: Default::default(),
|
||||
arc_map: Default::default(),
|
||||
reading: true,
|
||||
|
||||
@@ -257,6 +257,7 @@ impl Reader for BinaryReader<'_> {
|
||||
let mut visitor = Visitor {
|
||||
nodes: Pool::new(),
|
||||
unique_id_counter: 1,
|
||||
type_name_map: Default::default(),
|
||||
rc_map: Default::default(),
|
||||
arc_map: Default::default(),
|
||||
reading: true,
|
||||
|
||||
+16
-5
@@ -686,11 +686,17 @@ pub trait AbstractSceneGraph: 'static {
|
||||
) -> Option<&mut dyn AbstractSceneNode>;
|
||||
}
|
||||
|
||||
/// BaseSceneGraph is a dyn-compatible trait for all scene graphs to implement.
|
||||
/// Methods that would not be dyn-compatible are available through the
|
||||
/// [`SceneGraph`] trait.
|
||||
pub trait BaseSceneGraph: AbstractSceneGraph {
|
||||
type Prefab: PrefabData<Graph = Self>;
|
||||
type NodeContainer: PayloadContainer<Element = Self::Node>;
|
||||
type Node: SceneGraphNode<SceneGraph = Self, ResourceData = Self::Prefab>;
|
||||
|
||||
/// Generate a string that briefly summarizes the content of the graph for debugging.
|
||||
fn summary(&self) -> String;
|
||||
|
||||
/// Returns actual type id of the node.
|
||||
fn actual_type_id(&self, handle: Handle<Self::Node>) -> Option<TypeId>;
|
||||
|
||||
@@ -832,6 +838,9 @@ pub trait BaseSceneGraph: AbstractSceneGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/// SceneGraph is a non-dyn-compatible trait for all scene graphs to implement.
|
||||
/// To use a scene graph as a dyn object, use `dyn BaseSceneGraph` because
|
||||
/// [`BaseSceneGraph`] is dyn-compatible.
|
||||
pub trait SceneGraph: BaseSceneGraph {
|
||||
type ObjectType: Sized;
|
||||
/// Creates new iterator that iterates over internal collection giving (handle; node) pairs.
|
||||
@@ -1097,6 +1106,7 @@ pub trait SceneGraph: BaseSceneGraph {
|
||||
&self,
|
||||
from: Handle<Self::Node>,
|
||||
) -> impl Iterator<Item = (Handle<Self::Node>, &Self::Node)> {
|
||||
self.try_get_node(from).expect("Handle must be valid!");
|
||||
GraphTraverseIterator {
|
||||
graph: self,
|
||||
stack: vec![from],
|
||||
@@ -1148,7 +1158,6 @@ pub trait SceneGraph: BaseSceneGraph {
|
||||
let mut nodes_to_delete = Vec::new();
|
||||
for (_, node) in self.traverse_iter(instance_root) {
|
||||
if let Some(resource) = node.resource() {
|
||||
let kind = resource.kind();
|
||||
if let Some(model) = resource.state().data() {
|
||||
if !model
|
||||
.graph()
|
||||
@@ -1158,21 +1167,19 @@ pub trait SceneGraph: BaseSceneGraph {
|
||||
|
||||
Log::warn(format!(
|
||||
"Node {} ({}:{}) and its children will be deleted, because it \
|
||||
does not exist in the parent asset `{}`!",
|
||||
does not exist in the parent asset!",
|
||||
node.name(),
|
||||
node.self_handle().index(),
|
||||
node.self_handle().generation(),
|
||||
kind
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Log::warn(format!(
|
||||
"Node {} ({}:{}) and its children will be deleted, because its \
|
||||
parent asset `{}` failed to load!",
|
||||
parent asset failed to load!",
|
||||
node.name(),
|
||||
node.self_handle().index(),
|
||||
node.self_handle().generation(),
|
||||
kind
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1806,6 +1813,10 @@ mod test {
|
||||
type NodeContainer = NodeContainer;
|
||||
type Node = Node;
|
||||
|
||||
fn summary(&self) -> String {
|
||||
"Summary".to_string()
|
||||
}
|
||||
|
||||
fn root(&self) -> Handle<Self::Node> {
|
||||
self.root
|
||||
}
|
||||
|
||||
@@ -1059,8 +1059,8 @@ impl ResourceGraphVertex {
|
||||
|
||||
pub fn resolve(&self) {
|
||||
Log::info(format!(
|
||||
"Resolving {} resource from dependency graph...",
|
||||
self.resource.kind()
|
||||
"Resolving {:?} resource from dependency graph...",
|
||||
self.resource.resource_uuid()
|
||||
));
|
||||
|
||||
// Wait until resource is fully loaded, then resolve.
|
||||
@@ -2416,8 +2416,8 @@ impl Engine {
|
||||
if let ResourceEvent::Reloaded(resource) = event {
|
||||
if let Some(model) = resource.try_cast::<Model>() {
|
||||
Log::info(format!(
|
||||
"A model resource {} was reloaded, propagating changes...",
|
||||
model.kind()
|
||||
"A model resource {:?} was reloaded, propagating changes...",
|
||||
model.resource_uuid()
|
||||
));
|
||||
|
||||
// Build resource dependency graph and resolve it first.
|
||||
@@ -2799,7 +2799,10 @@ impl Engine {
|
||||
|
||||
// Deserialize prefab scene content.
|
||||
for (model, scene_state) in prefab_scenes {
|
||||
Log::info(format!("Deserializing {} prefab content...", model.kind()));
|
||||
Log::info(format!(
|
||||
"Deserializing {:?} prefab content...",
|
||||
model.resource_uuid()
|
||||
));
|
||||
|
||||
scene_state.deserialize_into_prefab_scene(
|
||||
&model,
|
||||
|
||||
+1
-2
@@ -255,8 +255,7 @@ impl TextureCache {
|
||||
drop(texture_data_guard);
|
||||
err_once!(
|
||||
texture_resource.key() as usize,
|
||||
"Failed to create GPU texture from {} texture. Reason: {:?}",
|
||||
texture_resource.kind(),
|
||||
"Failed to create GPU texture from texture. Reason: {:?}",
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
asset::{manager::ResourceManager, state::LoadError, untyped::ResourceKind, Resource},
|
||||
@@ -98,6 +101,24 @@ pub enum GltfMaterialError {
|
||||
Texture(TextureError),
|
||||
}
|
||||
|
||||
impl std::error::Error for GltfMaterialError {}
|
||||
|
||||
impl Display for GltfMaterialError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GltfMaterialError::ShaderLoadFailed => f.write_str("Shader load failed"),
|
||||
GltfMaterialError::InvalidIndex => f.write_str("Invalid material index"),
|
||||
GltfMaterialError::UnsupportedURI(uri) => {
|
||||
write!(f, "Unsupported material URI {uri:?}")
|
||||
}
|
||||
GltfMaterialError::TextureNotFound(uri) => write!(f, "Texture not found: {uri:?}"),
|
||||
GltfMaterialError::Load(error) => Display::fmt(error, f),
|
||||
GltfMaterialError::Base64(error) => Display::fmt(error, f),
|
||||
GltfMaterialError::Texture(error) => Display::fmt(error, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadError> for GltfMaterialError {
|
||||
fn from(error: LoadError) -> Self {
|
||||
GltfMaterialError::Load(error)
|
||||
|
||||
@@ -47,6 +47,7 @@ use crate::scene::Scene;
|
||||
use gltf::json;
|
||||
use gltf::Document;
|
||||
use gltf::Gltf;
|
||||
use std::fmt::Display;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
@@ -87,6 +88,27 @@ enum GltfLoadError {
|
||||
JSON(json::Error),
|
||||
}
|
||||
|
||||
impl std::error::Error for GltfLoadError {}
|
||||
|
||||
impl Display for GltfLoadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GltfLoadError::InvalidIndex => f.write_str("Invalid index"),
|
||||
GltfLoadError::InvalidPath => f.write_str("Invalid path"),
|
||||
GltfLoadError::UnsupportedURI(uri) => write!(f, "Unsupported URL {uri:?}"),
|
||||
GltfLoadError::MissingEmbeddedBin => f.write_str("Missing embedded bin"),
|
||||
GltfLoadError::Gltf(error) => Display::fmt(error, f),
|
||||
GltfLoadError::Texture(error) => Display::fmt(error, f),
|
||||
GltfLoadError::File(error) => Display::fmt(error, f),
|
||||
GltfLoadError::Base64(error) => Display::fmt(error, f),
|
||||
GltfLoadError::Load(error) => Display::fmt(error, f),
|
||||
GltfLoadError::Material(error) => Display::fmt(error, f),
|
||||
GltfLoadError::Surface(error) => Display::fmt(error, f),
|
||||
GltfLoadError::JSON(error) => Display::fmt(error, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<json::Error> for GltfLoadError {
|
||||
fn from(error: json::Error) -> Self {
|
||||
GltfLoadError::JSON(error)
|
||||
|
||||
@@ -41,6 +41,7 @@ use crate::{
|
||||
scene::mesh::buffer::{VertexAttributeUsage, VertexReadTrait},
|
||||
};
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::num::TryFromIntError;
|
||||
|
||||
/// This type represents any error that may occur while importing mesh data from glTF.
|
||||
@@ -83,6 +84,30 @@ pub enum SurfaceDataError {
|
||||
Fetch(buffer::VertexFetchError),
|
||||
}
|
||||
|
||||
impl std::error::Error for SurfaceDataError {}
|
||||
|
||||
impl Display for SurfaceDataError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SurfaceDataError::CountMismatch => f.write_str("Count mismatch"),
|
||||
SurfaceDataError::MissingPosition => f.write_str("Missing position"),
|
||||
SurfaceDataError::MissingNormal => f.write_str("Missing normal"),
|
||||
SurfaceDataError::MissingTexCoords => f.write_str("Missing texcoords"),
|
||||
SurfaceDataError::MissingBoneWeight => f.write_str("Missing bone weight"),
|
||||
SurfaceDataError::MissingBoneIndex => f.write_str("Missing bone index"),
|
||||
SurfaceDataError::InvalidBoneIndex => f.write_str("Invalid bone index"),
|
||||
SurfaceDataError::InvalidMode => f.write_str("Invalid mode"),
|
||||
SurfaceDataError::InvalidIndex => f.write_str("Invalid index"),
|
||||
SurfaceDataError::Int(error) => Display::fmt(error, f),
|
||||
SurfaceDataError::InvalidVertexCount(geometry_type, count) => {
|
||||
write!(f, "Cannot have {count} vertices {geometry_type}.")
|
||||
}
|
||||
SurfaceDataError::Validation(error) => Display::fmt(error, f),
|
||||
SurfaceDataError::Fetch(error) => Display::fmt(error, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidationError> for SurfaceDataError {
|
||||
fn from(error: ValidationError) -> Self {
|
||||
SurfaceDataError::Validation(error)
|
||||
@@ -108,6 +133,16 @@ pub enum GeometryType {
|
||||
TriangleFan,
|
||||
}
|
||||
|
||||
impl Display for GeometryType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GeometryType::Triangles => f.write_str("triangles"),
|
||||
GeometryType::TriangleStrip => f.write_str("triangle strip"),
|
||||
GeometryType::TriangleFan => f.write_str("triangle fan"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct BlendShapeInfo {
|
||||
pub name: String,
|
||||
|
||||
@@ -491,6 +491,12 @@ impl ModelResourceExtension for ModelResource {
|
||||
}
|
||||
|
||||
fn begin_instantiation<'a>(&'a self, dest_scene: &'a mut Scene) -> InstantiationContext<'a> {
|
||||
if !self.is_ok() {
|
||||
Log::err(format!(
|
||||
"Instantiating a model from a resource that is not loaded: {self:?}"
|
||||
));
|
||||
panic!();
|
||||
}
|
||||
InstantiationContext {
|
||||
model: self,
|
||||
dest_scene,
|
||||
|
||||
@@ -860,7 +860,7 @@ impl Display for ColorGradingLutCreationError {
|
||||
)
|
||||
}
|
||||
ColorGradingLutCreationError::Texture(v) => {
|
||||
write!(f, "Texture load error: {v:?}")
|
||||
write!(f, "Texture load error: {v}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ use bitflags::bitflags;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use fyrox_core::pool::ObjectOrVariant;
|
||||
use fyrox_graph::SceneGraphNode;
|
||||
use std::fmt::Write;
|
||||
use std::ops::Deref;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
@@ -127,7 +128,7 @@ impl GraphPerformanceStatistics {
|
||||
pub type NodePool = Pool<Node, NodeContainer>;
|
||||
|
||||
/// See module docs.
|
||||
#[derive(Debug, Reflect)]
|
||||
#[derive(Reflect)]
|
||||
pub struct Graph {
|
||||
#[reflect(hidden)]
|
||||
root: Handle<Node>,
|
||||
@@ -171,6 +172,22 @@ pub struct Graph {
|
||||
instance_id_map: FxHashMap<SceneNodeId, Handle<Node>>,
|
||||
}
|
||||
|
||||
impl Debug for Graph {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Graph")
|
||||
.field("physics", &self.physics)
|
||||
.field("physics2d", &self.physics2d)
|
||||
.field("sound_context", &self.sound_context)
|
||||
.field("performance_statistics", &self.performance_statistics)
|
||||
.field("event_broadcaster", &self.event_broadcaster)
|
||||
.field("lightmap", &self.lightmap)
|
||||
.field("instance_id_map", &self.instance_id_map)
|
||||
.finish()?;
|
||||
f.write_char('\n')?;
|
||||
f.write_str(&self.summary())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Graph {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_ex(
|
||||
@@ -268,7 +285,7 @@ fn clear_links(mut node: Node) -> Node {
|
||||
}
|
||||
|
||||
/// A set of switches that allows you to disable a particular step of graph update pipeline.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GraphUpdateSwitches {
|
||||
/// Enables the physics update to have a non-zero `dt`.
|
||||
pub physics_dt: bool,
|
||||
@@ -353,6 +370,30 @@ impl Graph {
|
||||
graph
|
||||
}
|
||||
|
||||
fn recursive_summary(&self, indent: usize, current: Handle<Node>, result: &mut String) {
|
||||
for _ in 0..indent {
|
||||
result.push_str(" ");
|
||||
}
|
||||
let Some(node) = self.try_get(current) else {
|
||||
use std::fmt::Write;
|
||||
writeln!(result, "{}: Failed to get", current).unwrap();
|
||||
return;
|
||||
};
|
||||
result.push_str(&node.summary());
|
||||
result.push('\n');
|
||||
for script in node.scripts() {
|
||||
for _ in 0..indent + 1 {
|
||||
result.push_str(" ");
|
||||
}
|
||||
result.push_str("+ Script: ");
|
||||
result.push_str(&script.summary());
|
||||
result.push('\n');
|
||||
}
|
||||
for child in node.children() {
|
||||
self.recursive_summary(indent + 1, *child, result);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets new root of the graph and attaches the old root to the new root. Old root becomes a child
|
||||
/// node of the new root.
|
||||
pub fn change_root_node(&mut self, root: Node) {
|
||||
@@ -1683,6 +1724,13 @@ impl BaseSceneGraph for Graph {
|
||||
type NodeContainer = NodeContainer;
|
||||
type Node = Node;
|
||||
|
||||
/// Create a brief debug summary of the contents of this graph.
|
||||
fn summary(&self) -> String {
|
||||
let mut result = String::new();
|
||||
self.recursive_summary(0, self.root, &mut result);
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn actual_type_id(&self, handle: Handle<Self::Node>) -> Option<TypeId> {
|
||||
self.pool
|
||||
@@ -2192,21 +2240,21 @@ mod test {
|
||||
let mesh_pivot = derived_scene
|
||||
.graph
|
||||
.find_by_name_from_root("MeshPivot")
|
||||
.unwrap()
|
||||
.expect("Missing MeshPivot")
|
||||
.0;
|
||||
let mesh = derived_scene
|
||||
.graph
|
||||
.find_by_name(mesh_pivot, "Mesh")
|
||||
.unwrap()
|
||||
.expect("Missing Mesh")
|
||||
.0;
|
||||
derived_scene
|
||||
.graph
|
||||
.find_by_name_from_root("AddedLater")
|
||||
.unwrap();
|
||||
.expect("Missing AddedLater");
|
||||
derived_scene
|
||||
.graph
|
||||
.find_by_name(mesh, "NewChildOfMesh")
|
||||
.unwrap();
|
||||
.expect("Missing NewChildOfMesh");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -517,6 +517,9 @@ fn make_trimesh(
|
||||
|
||||
for surface in mesh.surfaces() {
|
||||
let shared_data = surface.data();
|
||||
if !shared_data.is_ok() {
|
||||
continue;
|
||||
}
|
||||
let shared_data = shared_data.data_ref();
|
||||
|
||||
let vertices = &shared_data.vertex_buffer;
|
||||
|
||||
@@ -639,8 +639,10 @@ impl NodeTrait for Mesh {
|
||||
} else {
|
||||
for surface in self.surfaces.iter() {
|
||||
let data = surface.data();
|
||||
let data = data.data_ref();
|
||||
extend_aabb_from_vertex_buffer(&data.vertex_buffer, &mut bounding_box);
|
||||
if data.is_ok() {
|
||||
let data = data.data_ref();
|
||||
extend_aabb_from_vertex_buffer(&data.vertex_buffer, &mut bounding_box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,6 +723,9 @@ impl NodeTrait for Mesh {
|
||||
RdcControlFlow::Break
|
||||
} else {
|
||||
for surface in self.surfaces().iter() {
|
||||
if !surface.data_ref().is_ok() {
|
||||
continue;
|
||||
}
|
||||
let is_skinned = !surface.bones.is_empty();
|
||||
|
||||
let world = if is_skinned {
|
||||
|
||||
@@ -336,7 +336,7 @@ impl SurfaceData {
|
||||
if v1 == v2 || v1 == v3 || v2 == v3 {
|
||||
Log::warn(format!(
|
||||
"Degenerated triangle found when calculating tangents. Lighting may be \
|
||||
incorrect! Triangle indices: {triangle:?}. Triangle vertices: {v1} {v2} {v3}",
|
||||
incorrect! Triangle indices: {triangle:?}. Triangle vertices: {v1:?} {v2:?} {v3:?}",
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -435,7 +435,13 @@ impl SceneLoader {
|
||||
));
|
||||
|
||||
// Wait everything.
|
||||
join_all(used_resources.into_iter()).await;
|
||||
let results = join_all(used_resources.into_iter()).await;
|
||||
|
||||
for result in results {
|
||||
if let Err(err) = result {
|
||||
Log::err(format!("Scene resource loading error: {:?}", err));
|
||||
}
|
||||
}
|
||||
|
||||
Log::info(format!(
|
||||
"SceneLoader::finish() - All {used_resources_count} resources have finished loading."
|
||||
|
||||
@@ -134,6 +134,36 @@ pub enum RdcControlFlow {
|
||||
|
||||
/// A main trait for any scene graph node.
|
||||
pub trait NodeTrait: BaseNodeTrait + Reflect + Visit + ComponentProvider {
|
||||
/// Brief debugging information about this node.
|
||||
fn summary(&self) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut result = String::new();
|
||||
let type_name = self
|
||||
.type_name()
|
||||
.strip_prefix("fyrox_impl::scene::")
|
||||
.unwrap_or(self.type_name());
|
||||
write!(result, "{} {}<{}>", self.handle(), self.name(), type_name,).unwrap();
|
||||
if self.children().len() == 1 {
|
||||
result.push_str(" 1 child");
|
||||
} else if self.children().len() > 1 {
|
||||
write!(result, " {} children", self.children().len()).unwrap();
|
||||
}
|
||||
if self.script_count() > 0 {
|
||||
write!(result, " {} scripts", self.script_count()).unwrap();
|
||||
}
|
||||
if self.is_resource_instance_root() {
|
||||
result.push_str(" root");
|
||||
}
|
||||
let origin = self.original_handle_in_resource();
|
||||
if origin.is_some() {
|
||||
write!(result, " from:{}", origin).unwrap();
|
||||
}
|
||||
if let Some(r) = self.resource() {
|
||||
write!(result, " {}", r.summary()).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns axis-aligned bounding box in **local space** of the node.
|
||||
fn local_bounding_box(&self) -> AxisAlignedBoundingBox;
|
||||
|
||||
@@ -807,6 +837,7 @@ mod test {
|
||||
|
||||
let derived_data = derived_asset.data_ref();
|
||||
let derived_scene = derived_data.get_scene();
|
||||
|
||||
let pivot = derived_scene
|
||||
.graph
|
||||
.find_by_name_from_root("Pivot")
|
||||
|
||||
@@ -859,6 +859,20 @@ impl Script {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a brief summary of this script for debugging purposes.
|
||||
pub fn summary(&self) -> String {
|
||||
let mut summary = String::new();
|
||||
if self.initialized {
|
||||
summary.push_str("init ");
|
||||
}
|
||||
if self.started {
|
||||
summary.push_str("start ");
|
||||
}
|
||||
use std::fmt::Write;
|
||||
write!(summary, "{:?}", self.instance).unwrap();
|
||||
summary
|
||||
}
|
||||
|
||||
/// Performs downcasting to a particular type.
|
||||
#[inline]
|
||||
pub fn cast<T: ScriptTrait>(&self) -> Option<&T> {
|
||||
|
||||
+35
-23
@@ -39,8 +39,8 @@ use crate::{
|
||||
use fxhash::FxHashSet;
|
||||
pub use fyrox_core as core;
|
||||
use fyrox_core::{combine_uuids, log::Log, SafeLock};
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
use std::{any::Any, fmt::Display};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{Debug, Formatter},
|
||||
@@ -110,9 +110,9 @@ pub trait TypedResourceData: ResourceData + Default + TypeUuidProvider {}
|
||||
impl<T> TypedResourceData for T where T: ResourceData + Default + TypeUuidProvider {}
|
||||
|
||||
/// A trait for resource load error.
|
||||
pub trait ResourceLoadError: 'static + Debug + Send + Sync {}
|
||||
pub trait ResourceLoadError: 'static + Debug + Display + Send + Sync {}
|
||||
|
||||
impl<T> ResourceLoadError for T where T: 'static + Debug + Send + Sync {}
|
||||
impl<T> ResourceLoadError for T where T: 'static + Debug + Display + Send + Sync {}
|
||||
|
||||
/// Provides typed access to a resource state.
|
||||
pub struct ResourceHeaderGuard<'a, T>
|
||||
@@ -178,13 +178,24 @@ where
|
||||
/// ## Default State
|
||||
///
|
||||
/// Default state of the resource will be [`ResourceState::Ok`] with `T::default`.
|
||||
#[derive(Debug, Reflect)]
|
||||
#[derive(Reflect)]
|
||||
pub struct Resource<T: Debug> {
|
||||
untyped: UntypedResource,
|
||||
#[reflect(hidden)]
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Resource<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Resource<{}>({})",
|
||||
std::any::type_name::<T>(),
|
||||
self.untyped
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TypedResourceData> AsRef<UntypedResource> for Resource<T> {
|
||||
fn as_ref(&self) -> &UntypedResource {
|
||||
&self.untyped
|
||||
@@ -276,6 +287,10 @@ impl<T> Resource<T>
|
||||
where
|
||||
T: TypedResourceData,
|
||||
{
|
||||
/// Create a summary of this resource handle, wht UUID, embedded/external, and Ok, Pending, Error, etc.
|
||||
pub fn summary(&self) -> String {
|
||||
format!("{}", self.untyped)
|
||||
}
|
||||
/// Creates new resource in pending state.
|
||||
#[inline]
|
||||
pub fn new_pending(path: PathBuf, kind: ResourceKind) -> Self {
|
||||
@@ -523,15 +538,13 @@ where
|
||||
ResourceState::Pending { .. } => {
|
||||
write!(
|
||||
f,
|
||||
"Attempt to get reference to resource data while it is not loaded! Path is {}",
|
||||
self.guard.kind
|
||||
"Attempt to get reference to resource data while it is loading!"
|
||||
)
|
||||
}
|
||||
ResourceState::LoadError { .. } => {
|
||||
write!(
|
||||
f,
|
||||
"Attempt to get reference to resource data which failed to load! Path is {}",
|
||||
self.guard.kind
|
||||
"Attempt to get reference to resource data which failed to load!"
|
||||
)
|
||||
}
|
||||
ResourceState::Ok { ref data, .. } => data.fmt(f),
|
||||
@@ -549,15 +562,20 @@ where
|
||||
match self.guard.state {
|
||||
ResourceState::Pending { .. } => {
|
||||
panic!(
|
||||
"Attempt to get reference to resource data while it is not loaded! Path is {}",
|
||||
self.guard.kind
|
||||
"Attempt to get reference to resource data while it is loading! Type {}",
|
||||
std::any::type_name::<T>()
|
||||
)
|
||||
}
|
||||
ResourceState::LoadError { .. } => {
|
||||
panic!(
|
||||
"Attempt to get reference to resource data which failed to load! Path is {}",
|
||||
self.guard.kind
|
||||
)
|
||||
ResourceState::LoadError {
|
||||
ref path,
|
||||
ref error,
|
||||
} => {
|
||||
let path = if path.as_os_str().is_empty() {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
format!("{path:?}")
|
||||
};
|
||||
panic!("Attempt to get reference to resource data which failed to load! Type {}. Path: {path}. Error: {error:?}", std::any::type_name::<T>())
|
||||
}
|
||||
ResourceState::Ok { ref data, .. } => (&**data as &dyn Any)
|
||||
.downcast_ref()
|
||||
@@ -574,16 +592,10 @@ where
|
||||
let header = &mut *self.guard;
|
||||
match header.state {
|
||||
ResourceState::Pending { .. } => {
|
||||
panic!(
|
||||
"Attempt to get reference to resource data while it is not loaded! Path is {}",
|
||||
header.kind
|
||||
)
|
||||
panic!("Attempt to get reference to resource data while it is loading!")
|
||||
}
|
||||
ResourceState::LoadError { .. } => {
|
||||
panic!(
|
||||
"Attempt to get reference to resource data which failed to load! Path is {}",
|
||||
header.kind
|
||||
)
|
||||
panic!("Attempt to get reference to resource data which failed to load!")
|
||||
}
|
||||
ResourceState::Ok { ref mut data, .. } => (&mut **data as &mut dyn Any)
|
||||
.downcast_mut()
|
||||
|
||||
@@ -29,6 +29,7 @@ use crate::{
|
||||
use fyrox_core::reflect::ReflectHandle;
|
||||
use fyrox_core::warn;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Display;
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
@@ -65,11 +66,13 @@ impl DerefMut for WakersList {
|
||||
}
|
||||
}
|
||||
|
||||
/// Arbitrary loading error, that could be optionally be empty.
|
||||
/// Arbitrary loading error, that could be optionally be empty.
|
||||
#[derive(Reflect, Debug, Clone, Default)]
|
||||
#[reflect(hide_all)]
|
||||
pub struct LoadError(pub Option<Arc<dyn ResourceLoadError>>);
|
||||
|
||||
impl std::error::Error for LoadError {}
|
||||
|
||||
impl LoadError {
|
||||
/// Creates new loading error from a value of the given type.
|
||||
pub fn new<T: ResourceLoadError>(value: T) -> Self {
|
||||
@@ -77,6 +80,15 @@ impl LoadError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LoadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.0 {
|
||||
None => f.write_str("None"),
|
||||
Some(x) => Display::fmt(x, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct ResourceDataWrapper(pub Box<dyn ResourceData>);
|
||||
|
||||
@@ -33,6 +33,7 @@ use crate::{
|
||||
MODEL_RESOURCE_UUID, SHADER_RESOURCE_UUID, SOUND_BUFFER_RESOURCE_UUID, TEXTURE_RESOURCE_UUID,
|
||||
};
|
||||
use fyrox_core::err;
|
||||
use std::fmt::Write;
|
||||
use std::{
|
||||
error::Error,
|
||||
ffi::OsStr,
|
||||
@@ -166,6 +167,21 @@ pub struct ResourceHeader {
|
||||
old_format_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Display for ResourceHeader {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
ResourceKind::Embedded => f.write_str("Embed")?,
|
||||
ResourceKind::External => f.write_str("Extern")?,
|
||||
}
|
||||
f.write_char(':')?;
|
||||
match &self.state {
|
||||
ResourceState::Pending { .. } => f.write_str("Pending"),
|
||||
ResourceState::LoadError { path, error } => write!(f, "Error({path:?}, {error})"),
|
||||
ResourceState::Ok { .. } => f.write_str("Ok"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for ResourceHeader {
|
||||
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
|
||||
let mut region = visitor.enter_region(name)?;
|
||||
@@ -390,6 +406,16 @@ impl Default for UntypedResource {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UntypedResource {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(header) = self.0.try_lock() {
|
||||
Display::fmt(&header, f)
|
||||
} else {
|
||||
f.write_str("locked")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for UntypedResource {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Resource")
|
||||
|
||||
@@ -94,9 +94,50 @@ use fyrox_resource::{
|
||||
Resource, ResourceData,
|
||||
};
|
||||
use hrtf::HrirSphere;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::{error::Error, ops::Deref};
|
||||
use std::{fmt::Debug, fmt::Formatter, path::PathBuf, sync::Arc};
|
||||
use std::{fmt::Display, path::Path};
|
||||
|
||||
/// An error that occurs during HRIR sphere loading.
|
||||
pub struct HrtfError(pub hrtf::HrtfError);
|
||||
|
||||
impl std::error::Error for HrtfError {}
|
||||
|
||||
impl From<hrtf::HrtfError> for HrtfError {
|
||||
fn from(value: hrtf::HrtfError) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HrtfError> for hrtf::HrtfError {
|
||||
fn from(value: HrtfError) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for HrtfError {
|
||||
type Target = hrtf::HrtfError;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for HrtfError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HrtfError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.0 {
|
||||
hrtf::HrtfError::IoError(error) => Display::fmt(error, f),
|
||||
hrtf::HrtfError::InvalidFileFormat => f.write_str("Invalid file format"),
|
||||
hrtf::HrtfError::InvalidLength(n) => write!(f, "Invalid length {n}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// See module docs.
|
||||
#[derive(Clone, Debug, Default, Reflect)]
|
||||
@@ -255,8 +296,9 @@ impl ResourceLoader for HrirSphereLoader {
|
||||
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
|
||||
Box::pin(async move {
|
||||
let reader = io.file_reader(&path).await.map_err(LoadError::new)?;
|
||||
let hrir_sphere =
|
||||
HrirSphere::new(reader, context::SAMPLE_RATE).map_err(LoadError::new)?;
|
||||
let hrir_sphere = HrirSphere::new(reader, context::SAMPLE_RATE)
|
||||
.map_err(HrtfError::from)
|
||||
.map_err(LoadError::new)?;
|
||||
Ok(LoaderPayload::new(HrirSphereResourceData {
|
||||
hrir_sphere: Some(hrir_sphere),
|
||||
}))
|
||||
|
||||
@@ -242,6 +242,9 @@ impl SoundSource {
|
||||
match buffer.state().data() {
|
||||
None => return Err(SoundError::BufferFailedToLoad),
|
||||
Some(locked_buffer) => {
|
||||
if locked_buffer.duration() == Duration::ZERO {
|
||||
panic!("Zero duration buffer: {:?}", locked_buffer);
|
||||
}
|
||||
// Check new buffer if streaming - it must not be used by anyone else.
|
||||
if let SoundBuffer::Streaming(ref mut streaming) = *locked_buffer {
|
||||
if streaming.use_count != 0 {
|
||||
|
||||
@@ -80,6 +80,22 @@ where
|
||||
pub trait Control:
|
||||
BaseControl + Deref<Target = Widget> + DerefMut + Reflect + Visit + ComponentProvider
|
||||
{
|
||||
/// Brief debugging information about this node.
|
||||
fn summary(&self) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut result = String::new();
|
||||
let type_name = BaseControl::type_name(self)
|
||||
.strip_prefix("fyrox_ui::")
|
||||
.unwrap_or(BaseControl::type_name(self));
|
||||
write!(result, "{} {}<{}>", self.handle(), self.name(), type_name,).unwrap();
|
||||
if self.children().len() == 1 {
|
||||
result.push_str(" 1 child");
|
||||
} else if self.children().len() > 1 {
|
||||
write!(result, " {} children", self.children().len()).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// This method will be called before the widget is destroyed (dropped). At the moment, when this
|
||||
/// method is called, the widget is still in the widget graph and can be accessed via handles. It
|
||||
/// is guaranteed to be called once, and only if the widget is deleted via [`crate::widget::WidgetMessage::remove`].
|
||||
|
||||
+66
-2
@@ -327,7 +327,7 @@ use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
collections::{btree_set::BTreeSet, hash_map::Entry, VecDeque},
|
||||
error::Error,
|
||||
fmt::{Debug, Formatter},
|
||||
fmt::{Debug, Formatter, Write},
|
||||
ops::{Deref, DerefMut, Index, IndexMut},
|
||||
path::Path,
|
||||
sync::{
|
||||
@@ -686,7 +686,7 @@ pub enum RenderMode {
|
||||
OnChanges,
|
||||
}
|
||||
|
||||
#[derive(Reflect, Debug)]
|
||||
#[derive(Reflect)]
|
||||
pub struct UserInterface {
|
||||
screen_size: Vector2<f32>,
|
||||
nodes: WidgetPool,
|
||||
@@ -741,6 +741,48 @@ pub struct UserInterface {
|
||||
pub need_render: bool,
|
||||
}
|
||||
|
||||
impl Debug for UserInterface {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UserInterface")
|
||||
.field("screen_size", &self.screen_size)
|
||||
.field("drawing_context", &self.drawing_context)
|
||||
.field("visual_debug", &self.visual_debug)
|
||||
.field("root_canvas", &self.root_canvas)
|
||||
.field("picked_node", &self.picked_node)
|
||||
.field("prev_picked_node", &self.prev_picked_node)
|
||||
.field("captured_node", &self.captured_node)
|
||||
.field("keyboard_focus_node", &self.keyboard_focus_node)
|
||||
.field("cursor_position", &self.cursor_position)
|
||||
.field("style", &self.style)
|
||||
.field("receiver", &self.receiver)
|
||||
.field("sender", &self.sender)
|
||||
.field("stack", &self.stack)
|
||||
.field("picking_stack", &self.picking_stack)
|
||||
.field("bubble_queue", &self.bubble_queue)
|
||||
.field("drag_context", &self.drag_context)
|
||||
.field("mouse_state", &self.mouse_state)
|
||||
.field("keyboard_modifiers", &self.keyboard_modifiers)
|
||||
.field("cursor_icon", &self.cursor_icon)
|
||||
.field("active_tooltip", &self.active_tooltip)
|
||||
.field("methods_registry", &self.methods_registry)
|
||||
.field("clipboard", &self.clipboard)
|
||||
.field("layout_events_receiver", &self.layout_events_receiver)
|
||||
.field("layout_events_sender", &self.layout_events_sender)
|
||||
.field("z_index_update_set", &self.z_index_update_set)
|
||||
.field("default_font", &self.default_font)
|
||||
.field("double_click_entries", &self.double_click_entries)
|
||||
.field("double_click_time_slice", &self.double_click_time_slice)
|
||||
.field("tooltip_appear_delay", &self.tooltip_appear_delay)
|
||||
.field("standard_material", &self.standard_material)
|
||||
.field("render_target", &self.render_target)
|
||||
.field("render_mode", &self.render_mode)
|
||||
.field("need_render", &self.need_render)
|
||||
.finish()?;
|
||||
f.write_char('\n')?;
|
||||
f.write_str(&self.summary())
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for UserInterface {
|
||||
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
|
||||
let mut region = visitor.enter_region(name)?;
|
||||
@@ -1155,6 +1197,22 @@ impl UserInterface {
|
||||
ui
|
||||
}
|
||||
|
||||
fn recursive_summary(&self, indent: usize, current: Handle<UiNode>, result: &mut String) {
|
||||
for _ in 0..indent {
|
||||
result.push_str(" ");
|
||||
}
|
||||
let Some(node) = self.try_get(current) else {
|
||||
use std::fmt::Write;
|
||||
writeln!(result, "{}: Failed to get", current).unwrap();
|
||||
return;
|
||||
};
|
||||
result.push_str(&node.summary());
|
||||
result.push('\n');
|
||||
for child in node.children() {
|
||||
self.recursive_summary(indent + 1, *child, result);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_tooltip_appear_delay(&mut self, appear_delay: f32) {
|
||||
self.tooltip_appear_delay = appear_delay;
|
||||
}
|
||||
@@ -3275,6 +3333,12 @@ impl BaseSceneGraph for UserInterface {
|
||||
type NodeContainer = WidgetContainer;
|
||||
type Node = UiNode;
|
||||
|
||||
fn summary(&self) -> String {
|
||||
let mut result = String::new();
|
||||
self.recursive_summary(0, self.root_canvas, &mut result);
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn actual_type_id(&self, handle: Handle<Self::Node>) -> Option<TypeId> {
|
||||
self.nodes
|
||||
|
||||
Reference in New Issue
Block a user