Improving error reporting. (#853)

This commit is contained in:
b-guild
2025-10-08 19:37:33 +03:00
committed by GitHub
parent 42b0eead30
commit 7311ddbe21
31 changed files with 628 additions and 81 deletions
@@ -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 {
+2
View File
@@ -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 {
+10 -1
View File
@@ -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.
+47 -3
View File
@@ -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"),
+58 -10
View File
@@ -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);
}
}
+47 -2
View File
@@ -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 {
+16 -2
View File
@@ -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()))
+1
View File
@@ -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,
+1
View File
@@ -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
View File
@@ -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
}
+8 -5
View File
@@ -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
View File
@@ -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,
);
}
+22 -1
View File
@@ -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)
+22
View File
@@ -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)
+35
View File
@@ -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,
+6
View File
@@ -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,
+1 -1
View File
@@ -860,7 +860,7 @@ impl Display for ColorGradingLutCreationError {
)
}
ColorGradingLutCreationError::Texture(v) => {
write!(f, "Texture load error: {v:?}")
write!(f, "Texture load error: {v}")
}
}
}
+54 -6
View File
@@ -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");
}
}
+3
View File
@@ -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;
+7 -2
View File
@@ -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 {
+1 -1
View File
@@ -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:?}",
));
}
+7 -1
View File
@@ -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."
+31
View File
@@ -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")
+14
View File
@@ -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
View File
@@ -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()
+13 -1
View File
@@ -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>);
+26
View File
@@ -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")
+46 -4
View File
@@ -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),
}))
+3
View File
@@ -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 {
+16
View File
@@ -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
View File
@@ -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