use crate::{
FunctionCallContext, Namespace, ReflectBaseType, ReflectReference, access_map::ReflectAccessId,
script_value::ScriptValue,
};
use bevy_ecs::entity::Entity;
use bevy_mod_scripting_asset::Language;
use bevy_mod_scripting_derive::DebugWithTypeInfo;
use bevy_mod_scripting_display::{
DebugWithTypeInfo, DisplayWithTypeInfo, GetTypeInfo, OrFakeId, PrintReflectAsDebug,
WithTypeInfo,
};
use bevy_reflect::{ApplyError, PartialReflect, Reflect};
use std::{any::TypeId, borrow::Cow, error::Error, fmt::Display, panic::Location, sync::Arc};
#[derive(Clone)]
pub struct ReflectWrapper(Arc<dyn PartialReflect>);
impl bevy_mod_scripting_display::DebugWithTypeInfo for ReflectWrapper {
fn to_string_with_type_info(
&self,
f: &mut std::fmt::Formatter,
type_info_provider: Option<&dyn GetTypeInfo>,
) -> std::fmt::Result {
PrintReflectAsDebug::new_with_opt_info(&*self.0, type_info_provider)
.to_string_with_type_info(f, type_info_provider)
}
}
impl DisplayWithTypeInfo for ReflectWrapper {
fn display_with_type_info(
&self,
f: &mut std::fmt::Formatter,
type_info_provider: Option<&dyn GetTypeInfo>,
) -> std::fmt::Result {
PrintReflectAsDebug::new_with_opt_info(&*self.0, type_info_provider)
.to_string_with_type_info(f, type_info_provider)
}
}
#[derive(Clone)]
pub struct ExternalError(pub Arc<dyn Error + Send + Sync>);
impl bevy_mod_scripting_display::DebugWithTypeInfo for ExternalError {
fn to_string_with_type_info(
&self,
f: &mut std::fmt::Formatter,
_type_info_provider: Option<&dyn GetTypeInfo>,
) -> std::fmt::Result {
write!(f, "External error: {}", self.0)
}
}
impl DisplayWithTypeInfo for ExternalError {
fn display_with_type_info(
&self,
f: &mut std::fmt::Formatter,
_type_info_provider: Option<&dyn GetTypeInfo>,
) -> std::fmt::Result {
write!(f, "External error: {}", self.0)
}
}
#[derive(Clone, Reflect, DebugWithTypeInfo)]
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
#[reflect(opaque)]
pub enum InteropError {
NotImplemented,
ValueMismatch {
value: Box<ScriptValue>,
expected: Box<TypeId>,
},
LengthMismatch {
expected: usize,
got: usize,
},
FailedFromReflect {
type_id: Box<Option<TypeId>>,
reason: Box<String>,
},
TypeMismatch {
expected: Box<TypeId>,
got: Box<Option<TypeId>>,
},
StringTypeMismatch {
expected: Box<String>,
got: Box<Option<TypeId>>,
},
CannotClaimAccess {
base: Box<ReflectAccessId>,
location: Box<Option<Location<'static>>>,
context: Box<Cow<'static, str>>,
},
Invariant(Box<String>),
UnregisteredComponentOrResourceType {
type_name: Box<Cow<'static, str>>,
},
UnsupportedOperation {
base: Box<Option<TypeId>>,
value: Box<Option<ReflectWrapper>>,
operation: Box<String>,
},
MissingTypeData {
type_id: Box<TypeId>,
type_data: Box<String>,
},
MissingEntity(Entity),
FunctionInteropError {
function_name: Box<String>,
on: Box<Namespace>,
error: Box<InteropError>,
context: Box<Option<FunctionCallContext>>,
},
FunctionArgConversionError {
argument: Box<String>,
error: Box<InteropError>,
},
ArgumentCountMismatch {
expected: usize,
got: usize,
},
GarbageCollectedAllocation {
reference: Box<ReflectReference>,
},
UnregisteredReflectBase {
base: Box<ReflectBaseType>,
},
ReflectionPathError {
error: Box<String>,
reflected: Option<Box<ReflectReference>>,
},
CouldNotDowncastReference {
to: Box<TypeId>,
reference: Box<ReflectReference>,
},
MissingSchedule {
schedule_name: &'static str,
},
InvalidIndex {
index: Box<ScriptValue>,
reason: Box<String>,
},
MissingWorld,
External(ExternalError),
WithContext(
Box<Cow<'static, str>>,
Box<InteropError>,
),
}
impl InteropError {
pub fn unwrap_context(self) -> (Vec<Cow<'static, str>>, InteropError) {
let mut contexts = Vec::new();
let mut current = self;
while let InteropError::WithContext(context, err) = current {
contexts.push(*context);
current = *err;
}
(contexts, current)
}
pub fn with_context(self, context: impl Into<Cow<'static, str>>) -> Self {
Self::WithContext(Box::new(context.into()), Box::new(self))
}
pub fn external(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::External(ExternalError(Arc::new(error)))
}
pub fn external_boxed(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
Self::External(ExternalError(Arc::from(error)))
}
pub fn str(reason: &'static str) -> Self {
Self::External(ExternalError(Arc::from(Box::<
dyn std::error::Error + Send + Sync + 'static,
>::from(reason))))
}
pub fn string(reason: String) -> Self {
Self::External(ExternalError(Arc::from(Box::<
dyn std::error::Error + Send + Sync + 'static,
>::from(reason))))
}
pub fn value_mismatch(expected: TypeId, value: ScriptValue) -> Self {
Self::ValueMismatch {
value: Box::new(value),
expected: Box::new(expected),
}
}
pub fn length_mismatch(expected: usize, got: usize) -> Self {
Self::LengthMismatch { expected, got }
}
pub fn failed_from_reflect(type_id: Option<TypeId>, reason: impl Display) -> Self {
Self::FailedFromReflect {
type_id: Box::new(type_id),
reason: Box::new(reason.to_string()),
}
}
pub fn type_mismatch(expected: TypeId, got: Option<TypeId>) -> Self {
Self::TypeMismatch {
expected: Box::new(expected),
got: Box::new(got),
}
}
pub fn string_type_mismatch(expected: String, got: Option<TypeId>) -> Self {
Self::StringTypeMismatch {
expected: Box::new(expected),
got: Box::new(got),
}
}
pub fn cannot_claim_access(
base: ReflectAccessId,
location: Option<Location<'static>>,
context: impl Into<Cow<'static, str>>,
) -> Self {
Self::CannotClaimAccess {
base: Box::new(base),
location: Box::new(location),
context: Box::new(context.into()),
}
}
pub fn invariant(reason: impl Into<String>) -> Self {
Self::Invariant(Box::new(reason.into()))
}
pub fn unregistered_component_or_resource_type(
type_name: impl Into<Cow<'static, str>>,
) -> Self {
Self::UnregisteredComponentOrResourceType {
type_name: Box::new(type_name.into()),
}
}
pub fn unsupported_operation(
base: Option<TypeId>,
value: Option<Box<dyn PartialReflect>>,
op: impl Display,
) -> Self {
Self::UnsupportedOperation {
base: Box::new(base),
value: Box::new(value.map(|v| ReflectWrapper(Arc::from(v)))),
operation: Box::new(op.to_string()),
}
}
pub fn missing_type_data(type_id: TypeId, type_data: String) -> Self {
Self::MissingTypeData {
type_id: Box::new(type_id),
type_data: Box::new(type_data),
}
}
pub fn missing_entity(entity: Entity) -> Self {
Self::MissingEntity(entity)
}
pub fn reflect_apply_error(e: ApplyError) -> Self {
Self::External(ExternalError(Arc::new(e)))
}
pub fn function_interop_error(
function_name: impl Display,
on: Namespace,
error: InteropError,
context: Option<FunctionCallContext>,
) -> Self {
Self::FunctionInteropError {
function_name: Box::new(function_name.to_string()),
on: Box::new(on),
error: Box::new(error),
context: Box::new(context),
}
}
pub fn function_arg_conversion_error(argument: String, error: InteropError) -> Self {
Self::FunctionArgConversionError {
argument: Box::new(argument),
error: Box::new(error),
}
}
pub fn argument_count_mismatch(expected: usize, got: usize) -> Self {
Self::ArgumentCountMismatch { expected, got }
}
pub fn garbage_collected_allocation(reference: ReflectReference) -> Self {
Self::GarbageCollectedAllocation {
reference: Box::new(reference),
}
}
pub fn unregistered_base(base: ReflectBaseType) -> Self {
Self::UnregisteredReflectBase {
base: Box::new(base),
}
}
pub fn reflection_path_error(error: String, reflected: Option<ReflectReference>) -> Self {
Self::ReflectionPathError {
error: Box::new(error),
reflected: reflected.map(Box::new),
}
}
pub fn could_not_downcast(reference: ReflectReference, to: TypeId) -> Self {
Self::CouldNotDowncastReference {
to: Box::new(to),
reference: Box::new(reference),
}
}
pub fn missing_schedule(schedule_name: &'static str) -> Self {
Self::MissingSchedule { schedule_name }
}
pub fn invalid_index(index: ScriptValue, reason: impl Into<String>) -> Self {
Self::InvalidIndex {
index: Box::new(index),
reason: Box::new(reason.into()),
}
}
pub fn missing_world() -> Self {
Self::MissingWorld
}
pub fn missing_function(
function_name: impl Display,
on: Namespace,
context: Option<FunctionCallContext>,
) -> Self {
Self::FunctionInteropError {
function_name: Box::new(function_name.to_string()),
on: Box::new(on),
error: Box::new(InteropError::str("Function not found")),
context: Box::new(context),
}
}
}
impl std::fmt::Display for InteropError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(&WithTypeInfo::new(self), f)
}
}
impl std::error::Error for InteropError {}
impl DisplayWithTypeInfo for InteropError {
fn display_with_type_info(
&self,
f: &mut std::fmt::Formatter,
type_info_provider: Option<&dyn GetTypeInfo>,
) -> std::fmt::Result {
match self {
InteropError::NotImplemented => {
write!(f, "This feature is not implemented")
}
InteropError::ValueMismatch { value, expected } => {
write!(
f,
"Value type mismatch: expected {}, got {}",
WithTypeInfo::new_with_opt_info(expected, type_info_provider),
WithTypeInfo::new_with_opt_info(value, type_info_provider)
)
}
InteropError::LengthMismatch { expected, got } => {
write!(f, "Length mismatch: expected {expected}, got {got}")
}
InteropError::FailedFromReflect { type_id, reason } => {
write!(
f,
"Failed to convert from reflect {}: {}",
WithTypeInfo::new_with_opt_info(
&type_id.as_ref().or_fake_id(),
type_info_provider
),
reason
)
}
InteropError::TypeMismatch { expected, got } => {
write!(
f,
"Type mismatch: expected {}, got {}",
WithTypeInfo::new_with_opt_info(expected, type_info_provider),
WithTypeInfo::new_with_opt_info(&got.as_ref().or_fake_id(), type_info_provider)
)
}
InteropError::StringTypeMismatch { expected, got } => {
write!(
f,
"Type mismatch: expected {}, got {}",
expected,
WithTypeInfo::new_with_opt_info(&got.as_ref().or_fake_id(), type_info_provider)
)
}
InteropError::CannotClaimAccess {
base,
location,
context,
} => {
if let Some(location) = location.as_ref() {
write!(
f,
"Cannot claim access to {} at {}:{}:{}: {}",
WithTypeInfo::new_with_opt_info(base, type_info_provider),
location.file(),
location.line(),
location.column(),
context
)
} else {
write!(
f,
"Cannot claim access to {}: {}",
WithTypeInfo::new_with_opt_info(base, type_info_provider),
context
)
}
}
InteropError::Invariant(i) => {
write!(f, "Invariant broken: {i}")
}
InteropError::UnregisteredComponentOrResourceType { type_name } => {
write!(f, "Unregistered component or resource type: {type_name}")
}
InteropError::UnsupportedOperation {
base,
value,
operation,
} => {
write!(
f,
"Unsupported operation on {}{}: {}",
WithTypeInfo::new_with_opt_info(
&base.as_ref().or_fake_id(),
type_info_provider
),
if let Some(value) = value.as_ref() {
format!(
" with value {}",
WithTypeInfo::new_with_opt_info(value, type_info_provider)
)
} else {
"".to_string()
},
operation
)
}
InteropError::MissingTypeData { type_id, type_data } => {
write!(
f,
"Missing type data {} for type: {}",
type_data,
WithTypeInfo::new_with_opt_info(type_id, type_info_provider)
)
}
InteropError::MissingEntity(entity) => {
if *entity == Entity::PLACEHOLDER || entity.index().index() == 0 {
write!(
f,
"Invalid entity: {entity}. Are you trying to use an entity in a callback in which it's unavailable?"
)
} else {
write!(f, "Missing or invalid entity: {entity}")
}
}
InteropError::FunctionInteropError {
function_name,
on,
error,
context,
} => {
write!(
f,
"Error {}\n in function {} on {}:\n {}",
context
.clone()
.unwrap_or(FunctionCallContext::new(Language::Unknown)),
function_name,
WithTypeInfo::new_with_opt_info(on, type_info_provider),
error
)
}
InteropError::FunctionArgConversionError { argument, error } => {
write!(
f,
"Error converting argument {}: {}",
argument,
WithTypeInfo::new_with_opt_info(error, type_info_provider)
)
}
InteropError::ArgumentCountMismatch { expected, got } => {
write!(f, "Argument count mismatch: expected {expected}, got {got}")
}
InteropError::GarbageCollectedAllocation { reference } => {
write!(
f,
"Garbage collected allocation used: {}",
WithTypeInfo::new_with_opt_info(reference, type_info_provider)
)
}
InteropError::UnregisteredReflectBase { base } => {
write!(
f,
"Unregistered reflect base: {}",
WithTypeInfo::new_with_opt_info(base, type_info_provider)
)
}
InteropError::ReflectionPathError { error, reflected } => {
write!(
f,
"Reflection path error: {}{}",
error,
if let Some(reflected) = reflected.as_ref() {
format!(
"\nOn value: {}",
WithTypeInfo::new_with_opt_info(reflected, type_info_provider)
)
} else {
"".to_string()
}
)
}
InteropError::CouldNotDowncastReference { to, reference } => {
write!(
f,
"Could not downcast reference {} to type {}",
WithTypeInfo::new_with_opt_info(reference, type_info_provider),
WithTypeInfo::new_with_opt_info(to, type_info_provider)
)
}
InteropError::MissingSchedule { schedule_name } => {
write!(f, "Missing schedule: {schedule_name}")
}
InteropError::InvalidIndex { index, reason } => {
write!(
f,
"Invalid index {}: {}",
WithTypeInfo::new_with_opt_info(index, type_info_provider),
reason
)
}
InteropError::MissingWorld => {
write!(f, "Missing world")
}
InteropError::External(external_error) => {
write!(
f,
"External error: {}",
WithTypeInfo::new_with_opt_info(external_error, type_info_provider)
)
}
InteropError::WithContext(cow, interop_error) => {
write!(
f,
"{}: {}",
cow,
WithTypeInfo::new_with_opt_info(interop_error, type_info_provider)
)
}
}
}
}
#[cfg(test)]
mod test {
use bevy_reflect::TypeRegistry;
use super::*;
#[test]
fn test_script_value_prints_using_type_data() {
let mut registry = TypeRegistry::empty();
registry.register::<ScriptValue>();
pretty_assertions::assert_str_eq!(
format!(
"{:?}",
PrintReflectAsDebug::new_with_opt_info(&ScriptValue::Integer(1), Some(®istry))
),
"1",
);
}
}