#[cfg(feature = "bevy-reflection")]
use bevy::prelude::*;
#[cfg(feature = "bevy-reflection")]
use bevy::reflect::*;
#[cfg(feature = "bevy-reflection")]
use ::serde::{Deserialize, Serialize};
#[cfg(not(feature = "bevy-reflection"))]
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info, warn, error};
use crate::server::error::{Error, Result};
fn default_type_id() -> TypeId {
TypeId::of::<()>()
}
use crate::server::brp_messages::ComponentValue;
#[derive(Clone)]
pub struct BevyReflectionInspector {
reflection_cache: Arc<RwLock<HashMap<String, ReflectionMetadata>>>,
custom_inspectors: Arc<RwLock<HashMap<String, Box<dyn CustomInspector + Send + Sync>>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReflectionMetadata {
pub type_name: String,
#[serde(skip, default = "default_type_id")]
pub type_id: TypeId,
pub is_reflected: bool,
pub fields: Vec<FieldMetadata>,
pub type_category: TypeCategory,
pub type_info: Option<Value>,
pub last_updated: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldMetadata {
pub name: String,
pub type_name: String,
pub index: usize,
pub is_reflected: bool,
pub inspector_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TypeCategory {
Struct,
TupleStruct,
Enum,
Array,
List,
Map,
Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReflectionInspectionResult {
pub component_type: String,
pub metadata: ReflectionMetadata,
pub field_values: HashMap<String, InspectedValue>,
pub errors: Vec<String>,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InspectedValue {
pub name: String,
pub raw_value: Value,
pub type_info: String,
pub display_value: String,
pub inspectable: bool,
pub children: Option<Vec<InspectedValue>>,
}
pub trait CustomInspector: Any + Send + Sync {
fn inspect(&self, value: &ComponentValue, type_name: &str) -> Result<InspectedValue>;
fn name(&self) -> &str;
fn supported_types(&self) -> Vec<String>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReflectionDiffResult {
pub type_name: String,
pub field_diffs: HashMap<String, FieldDiff>,
pub summary: DiffSummary,
pub change_descriptions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldDiff {
pub field_name: String,
pub field_type: String,
pub old_value: Option<InspectedValue>,
pub new_value: Option<InspectedValue>,
pub change_type: ChangeType,
pub significance: ChangeSeverity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChangeType {
Added,
Removed,
Modified,
TypeChanged,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChangeSeverity {
Trivial, Minor, Major, Critical, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiffSummary {
pub total_fields: usize,
pub changed_fields: usize,
pub added_fields: usize,
pub removed_fields: usize,
pub critical_changes: usize,
pub major_changes: usize,
pub minor_changes: usize,
pub trivial_changes: usize,
}
impl BevyReflectionInspector {
pub fn new() -> Self {
let mut inspector = Self {
reflection_cache: Arc::new(RwLock::new(HashMap::new())),
custom_inspectors: Arc::new(RwLock::new(HashMap::new())),
};
inspector.register_default_inspectors();
inspector
}
fn register_default_inspectors(&mut self) {
debug!("Registering default custom inspectors");
}
pub async fn register_custom_inspector(&self, inspector: Box<dyn CustomInspector + Send + Sync>) {
let inspector_name = inspector.name().to_string();
let mut inspectors = self.custom_inspectors.write().await;
inspectors.insert(inspector_name.clone(), inspector);
info!("Registered custom inspector: {}", inspector_name);
}
#[cfg(feature = "bevy-reflection")]
pub async fn discover_component_types(&self, type_registry: &TypeRegistry) -> Result<Vec<ReflectionMetadata>> {
debug!("Discovering component types from TypeRegistry");
let mut discovered_types = Vec::new();
for registration in type_registry.iter() {
let type_info = registration.type_info();
let metadata = self.build_reflection_metadata(registration).await?;
discovered_types.push(metadata);
}
info!("Discovered {} reflected component types", discovered_types.len());
Ok(discovered_types)
}
#[cfg(feature = "bevy-reflection")]
async fn build_reflection_metadata(&self, registration: &TypeRegistration) -> Result<ReflectionMetadata> {
let type_info = registration.type_info();
let type_name = type_info.type_path().to_string();
let type_id = registration.type_id();
let (type_category, fields) = match type_info {
TypeInfo::Struct(struct_info) => {
let mut field_metadata = Vec::new();
for (index, field) in struct_info.iter().enumerate() {
field_metadata.push(FieldMetadata {
name: field.name().to_string(),
type_name: field.type_path().to_string(),
index,
is_reflected: true, inspector_name: self.find_inspector_for_type(field.type_path()).await,
});
}
(TypeCategory::Struct, field_metadata)
}
TypeInfo::TupleStruct(tuple_info) => {
let mut field_metadata = Vec::new();
for (index, field) in tuple_info.iter().enumerate() {
field_metadata.push(FieldMetadata {
name: format!("field_{}", index),
type_name: field.type_path().to_string(),
index,
is_reflected: true,
inspector_name: self.find_inspector_for_type(field.type_path()).await,
});
}
(TypeCategory::TupleStruct, field_metadata)
}
TypeInfo::Enum(_) => (TypeCategory::Enum, Vec::new()),
TypeInfo::Array(_) => (TypeCategory::Array, Vec::new()),
TypeInfo::List(_) => (TypeCategory::List, Vec::new()),
TypeInfo::Map(_) => (TypeCategory::Map, Vec::new()),
TypeInfo::Set(_) => (TypeCategory::Map, Vec::new()),
TypeInfo::Tuple(_) => (TypeCategory::TupleStruct, Vec::new()),
TypeInfo::Opaque(_) => (TypeCategory::Value, Vec::new()),
};
Ok(ReflectionMetadata {
type_name: type_name.clone(),
type_id,
is_reflected: true,
fields,
type_category,
type_info: Some(self.serialize_type_info(&type_info).await),
last_updated: chrono::Utc::now().timestamp_micros() as u64,
})
}
#[cfg(feature = "bevy-reflection")]
async fn serialize_type_info(&self, type_info: &TypeInfo) -> Value {
match type_info {
TypeInfo::Struct(struct_info) => json!({
"type": "struct",
"name": struct_info.type_path(),
"field_count": struct_info.field_len(),
"fields": struct_info.iter().map(|field| {
json!({
"name": field.name(),
"type": field.type_path(),
})
}).collect::<Vec<_>>()
}),
TypeInfo::Enum(enum_info) => json!({
"type": "enum",
"name": enum_info.type_path(),
"variant_count": enum_info.variant_len(),
}),
_ => json!({
"type": "other",
"name": type_info.type_path(),
}),
}
}
async fn find_inspector_for_type(&self, type_path: &str) -> Option<String> {
let inspectors = self.custom_inspectors.read().await;
for inspector in inspectors.values() {
if inspector.supported_types().contains(&type_path.to_string()) {
return Some(inspector.name().to_string());
}
}
None
}
pub async fn inspect_component(&self, component_type: &str, component_value: &ComponentValue) -> Result<ReflectionInspectionResult> {
debug!("Inspecting component: {}", component_type);
let metadata = self.get_or_create_metadata(component_type).await?;
let mut field_values = HashMap::new();
let mut errors = Vec::new();
if let Some(inspector_name) = &metadata.fields.get(0).and_then(|f| f.inspector_name.as_ref()) {
let inspectors = self.custom_inspectors.read().await;
if let Some(inspector) = inspectors.get(inspector_name.as_str()) {
match inspector.inspect(component_value, component_type) {
Ok(inspected_value) => {
field_values.insert("root".to_string(), inspected_value);
}
Err(e) => {
errors.push(format!("Custom inspector error: {}", e));
}
}
}
}
if field_values.is_empty() {
match self.inspect_value_generic(component_value, component_type).await {
Ok(inspected_value) => {
field_values.insert("root".to_string(), inspected_value);
}
Err(e) => {
errors.push(format!("Generic inspection error: {}", e));
}
}
}
Ok(ReflectionInspectionResult {
component_type: component_type.to_string(),
metadata,
field_values,
errors,
timestamp: chrono::Utc::now().timestamp_micros() as u64,
})
}
pub async fn inspect_value_generic(&self, value: &ComponentValue, type_name: &str) -> Result<InspectedValue> {
let inspected = match value {
Value::Object(obj) => {
let mut children = Vec::new();
for (key, val) in obj {
let child = Box::pin(self.inspect_value_generic(val, "unknown")).await?;
children.push(InspectedValue {
name: key.clone(),
raw_value: val.clone(),
type_info: self.infer_type_from_value(val),
display_value: self.format_display_value(val),
inspectable: self.is_value_inspectable(val),
children: None,
});
}
InspectedValue {
name: "root".to_string(),
raw_value: value.clone(),
type_info: type_name.to_string(),
display_value: format!("{} {{ {} fields }}", type_name, obj.len()),
inspectable: true,
children: Some(children),
}
}
Value::Array(arr) => {
let mut children = Vec::new();
for (i, val) in arr.iter().enumerate() {
let child = Box::pin(self.inspect_value_generic(val, "unknown")).await?;
children.push(InspectedValue {
name: format!("[{}]", i),
raw_value: val.clone(),
type_info: self.infer_type_from_value(val),
display_value: self.format_display_value(val),
inspectable: self.is_value_inspectable(val),
children: None,
});
}
InspectedValue {
name: "root".to_string(),
raw_value: value.clone(),
type_info: format!("Vec<T> (length: {})", arr.len()),
display_value: format!("Array[{}]", arr.len()),
inspectable: true,
children: Some(children),
}
}
_ => {
InspectedValue {
name: "root".to_string(),
raw_value: value.clone(),
type_info: self.infer_type_from_value(value),
display_value: self.format_display_value(value),
inspectable: false,
children: None,
}
}
};
Ok(inspected)
}
fn infer_type_from_value(&self, value: &Value) -> String {
match value {
Value::Null => "Option<T> (None)".to_string(),
Value::Bool(_) => "bool".to_string(),
Value::Number(n) => {
if n.is_i64() {
"i64".to_string()
} else if n.is_u64() {
"u64".to_string()
} else {
"f64".to_string()
}
}
Value::String(_) => "String".to_string(),
Value::Array(_) => "Vec<T>".to_string(),
Value::Object(_) => "Struct".to_string(),
}
}
fn format_display_value(&self, value: &Value) -> String {
match value {
Value::Null => "None".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => format!("\"{}\"", s),
Value::Array(arr) => format!("[{} items]", arr.len()),
Value::Object(obj) => format!("{{ {} fields }}", obj.len()),
}
}
fn is_value_inspectable(&self, value: &Value) -> bool {
matches!(value, Value::Object(_) | Value::Array(_))
}
async fn get_or_create_metadata(&self, component_type: &str) -> Result<ReflectionMetadata> {
let mut cache = self.reflection_cache.write().await;
if let Some(metadata) = cache.get(component_type) {
return Ok(metadata.clone());
}
let metadata = ReflectionMetadata {
type_name: component_type.to_string(),
type_id: TypeId::of::<()>(), is_reflected: false,
fields: Vec::new(),
type_category: TypeCategory::Value,
type_info: None,
last_updated: chrono::Utc::now().timestamp_micros() as u64,
};
cache.insert(component_type.to_string(), metadata.clone());
Ok(metadata)
}
async fn field_map(
&self,
value: &ComponentValue,
) -> Result<HashMap<String, InspectedValue>> {
let mut fields = HashMap::new();
match value {
Value::Object(obj) => {
for (name, field_value) in obj {
fields.insert(
name.clone(),
self.inspect_value_generic(field_value, name).await?,
);
}
}
other => {
fields.insert(
"root".to_string(),
self.inspect_value_generic(other, "root").await?,
);
}
}
Ok(fields)
}
pub async fn diff_components(
&self,
component_type: &str,
old_value: &ComponentValue,
new_value: &ComponentValue,
) -> Result<ReflectionDiffResult> {
debug!("Diffing components of type: {}", component_type);
let old_fields = self.field_map(old_value).await?;
let new_fields = self.field_map(new_value).await?;
let mut field_diffs = HashMap::new();
let mut summary = DiffSummary {
total_fields: 0,
changed_fields: 0,
added_fields: 0,
removed_fields: 0,
critical_changes: 0,
major_changes: 0,
minor_changes: 0,
trivial_changes: 0,
};
let all_field_names: std::collections::HashSet<String> = old_fields
.keys()
.chain(new_fields.keys())
.cloned()
.collect();
summary.total_fields = all_field_names.len();
for field_name in all_field_names {
let old_field = old_fields.get(&field_name);
let new_field = new_fields.get(&field_name);
let (change_type, severity) = match (old_field, new_field) {
(None, Some(_)) => (ChangeType::Added, ChangeSeverity::Minor),
(Some(_), None) => (ChangeType::Removed, ChangeSeverity::Major),
(Some(old), Some(new)) => {
if old.type_info != new.type_info {
(ChangeType::TypeChanged, ChangeSeverity::Critical)
} else if old.raw_value != new.raw_value {
let severity = self.assess_change_severity(&old.raw_value, &new.raw_value);
(ChangeType::Modified, severity)
} else {
continue; }
}
(None, None) => continue, };
match change_type {
ChangeType::Added => summary.added_fields += 1,
ChangeType::Removed => summary.removed_fields += 1,
_ => {}
}
match severity {
ChangeSeverity::Trivial => summary.trivial_changes += 1,
ChangeSeverity::Minor => summary.minor_changes += 1,
ChangeSeverity::Major => summary.major_changes += 1,
ChangeSeverity::Critical => summary.critical_changes += 1,
}
summary.changed_fields += 1;
let field_diff = FieldDiff {
field_name: field_name.clone(),
field_type: old_field
.or(new_field)
.map(|f| f.type_info.clone())
.unwrap_or_else(|| "unknown".to_string()),
old_value: old_field.cloned(),
new_value: new_field.cloned(),
change_type,
significance: severity,
};
field_diffs.insert(field_name, field_diff);
}
let change_descriptions = self.generate_change_descriptions(&field_diffs);
Ok(ReflectionDiffResult {
type_name: component_type.to_string(),
field_diffs,
summary,
change_descriptions,
})
}
pub fn assess_change_severity(&self, old_value: &Value, new_value: &Value) -> ChangeSeverity {
match (old_value, new_value) {
(Value::Number(old_num), Value::Number(new_num)) => {
if let (Some(old_f), Some(new_f)) = (old_num.as_f64(), new_num.as_f64()) {
let diff = (old_f - new_f).abs();
let relative_change = diff / old_f.abs().max(1.0);
if relative_change < 0.001 {
ChangeSeverity::Trivial
} else if relative_change < 0.1 {
ChangeSeverity::Minor
} else {
ChangeSeverity::Major
}
} else {
ChangeSeverity::Minor
}
}
(Value::String(old_s), Value::String(new_s)) => {
if old_s.len() != new_s.len() || old_s != new_s {
ChangeSeverity::Minor
} else {
ChangeSeverity::Trivial
}
}
(Value::Bool(_), Value::Bool(_)) => ChangeSeverity::Minor,
(Value::Array(old_arr), Value::Array(new_arr)) => {
if old_arr.len() != new_arr.len() {
ChangeSeverity::Major
} else {
ChangeSeverity::Minor
}
}
(Value::Object(old_obj), Value::Object(new_obj)) => {
if old_obj.len() != new_obj.len() {
ChangeSeverity::Major
} else {
ChangeSeverity::Minor
}
}
_ => ChangeSeverity::Major, }
}
fn generate_change_descriptions(&self, field_diffs: &HashMap<String, FieldDiff>) -> Vec<String> {
let mut descriptions = Vec::new();
for (field_name, diff) in field_diffs {
let description = match &diff.change_type {
ChangeType::Added => {
format!("Added field '{}' with value: {}",
field_name,
diff.new_value.as_ref()
.map(|v| v.display_value.clone())
.unwrap_or_else(|| "unknown".to_string())
)
}
ChangeType::Removed => {
format!("Removed field '{}' (was: {})",
field_name,
diff.old_value.as_ref()
.map(|v| v.display_value.clone())
.unwrap_or_else(|| "unknown".to_string())
)
}
ChangeType::Modified => {
format!("Modified field '{}': {} -> {}",
field_name,
diff.old_value.as_ref()
.map(|v| v.display_value.clone())
.unwrap_or_else(|| "unknown".to_string()),
diff.new_value.as_ref()
.map(|v| v.display_value.clone())
.unwrap_or_else(|| "unknown".to_string())
)
}
ChangeType::TypeChanged => {
format!("Type changed for field '{}': {} -> {}",
field_name,
diff.old_value.as_ref()
.map(|v| v.type_info.clone())
.unwrap_or_else(|| "unknown".to_string()),
diff.new_value.as_ref()
.map(|v| v.type_info.clone())
.unwrap_or_else(|| "unknown".to_string())
)
}
};
descriptions.push(description);
}
descriptions
}
pub async fn get_all_reflection_metadata(&self) -> HashMap<String, ReflectionMetadata> {
self.reflection_cache.read().await.clone()
}
pub async fn clear_cache(&self) {
let mut cache = self.reflection_cache.write().await;
cache.clear();
info!("Cleared reflection cache");
}
pub async fn get_reflection_stats(&self) -> Value {
let cache = self.reflection_cache.read().await;
let inspectors = self.custom_inspectors.read().await;
json!({
"cached_types": cache.len(),
"custom_inspectors": inspectors.len(),
"reflected_types": cache.values().filter(|m| m.is_reflected).count(),
"total_fields": cache.values().map(|m| m.fields.len()).sum::<usize>(),
})
}
}
impl Default for BevyReflectionInspector {
fn default() -> Self {
Self::new()
}
}
pub struct TransformInspector;
impl CustomInspector for TransformInspector {
fn inspect(&self, value: &ComponentValue, _type_name: &str) -> Result<InspectedValue> {
if let Value::Object(obj) = value {
let mut children = Vec::new();
if let Some(translation) = obj.get("translation") {
children.push(InspectedValue {
name: "translation".to_string(),
raw_value: translation.clone(),
type_info: "Vec3".to_string(),
display_value: self.format_vec3(translation),
inspectable: true,
children: None,
});
}
if let Some(rotation) = obj.get("rotation") {
children.push(InspectedValue {
name: "rotation".to_string(),
raw_value: rotation.clone(),
type_info: "Quat".to_string(),
display_value: self.format_quat(rotation),
inspectable: true,
children: None,
});
}
if let Some(scale) = obj.get("scale") {
children.push(InspectedValue {
name: "scale".to_string(),
raw_value: scale.clone(),
type_info: "Vec3".to_string(),
display_value: self.format_vec3(scale),
inspectable: true,
children: None,
});
}
Ok(InspectedValue {
name: "Transform".to_string(),
raw_value: value.clone(),
type_info: "bevy_transform::components::transform::Transform".to_string(),
display_value: "Transform Component".to_string(),
inspectable: true,
children: Some(children),
})
} else {
Err(Error::DebugError("Transform component should be an object".to_string()))
}
}
fn name(&self) -> &str {
"TransformInspector"
}
fn supported_types(&self) -> Vec<String> {
vec!["bevy_transform::components::transform::Transform".to_string()]
}
}
impl TransformInspector {
fn format_vec3(&self, value: &Value) -> String {
if let Value::Object(obj) = value {
let x = obj.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
let y = obj.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
let z = obj.get("z").and_then(|v| v.as_f64()).unwrap_or(0.0);
format!("({:.2}, {:.2}, {:.2})", x, y, z)
} else {
"Invalid Vec3".to_string()
}
}
fn format_quat(&self, value: &Value) -> String {
if let Value::Object(obj) = value {
let x = obj.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
let y = obj.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
let z = obj.get("z").and_then(|v| v.as_f64()).unwrap_or(0.0);
let w = obj.get("w").and_then(|v| v.as_f64()).unwrap_or(1.0);
let euler = self.quat_to_euler(x, y, z, w);
format!("Quat(x:{:.2}, y:{:.2}, z:{:.2}, w:{:.2}) ≈ ({:.1}°, {:.1}°, {:.1}°)",
x, y, z, w, euler.0, euler.1, euler.2)
} else {
"Invalid Quat".to_string()
}
}
fn quat_to_euler(&self, x: f64, y: f64, z: f64, w: f64) -> (f64, f64, f64) {
let yaw = (2.0 * (w * y + z * x)).atan2(1.0 - 2.0 * (y * y + z * z));
let pitch = (2.0 * (w * z - x * y)).asin();
let roll = (2.0 * (w * x + y * z)).atan2(1.0 - 2.0 * (x * x + z * z));
(yaw.to_degrees(), pitch.to_degrees(), roll.to_degrees())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_reflection_inspector_creation() {
let inspector = BevyReflectionInspector::new();
let stats = inspector.get_reflection_stats().await;
assert!(stats.get("cached_types").is_some());
assert!(stats.get("custom_inspectors").is_some());
}
#[tokio::test]
async fn test_generic_inspection() {
let inspector = BevyReflectionInspector::new();
let test_value = json!({
"x": 1.0,
"y": 2.0,
"z": 3.0
});
let result = inspector.inspect_value_generic(&test_value, "TestType").await.unwrap();
assert_eq!(result.type_info, "TestType");
assert!(result.inspectable);
assert!(result.children.is_some());
}
#[tokio::test]
async fn test_transform_inspector() {
let inspector = TransformInspector;
let transform_value = json!({
"translation": {"x": 1.0, "y": 2.0, "z": 3.0},
"rotation": {"x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0}
});
let result = inspector.inspect(&transform_value, "Transform").unwrap();
assert_eq!(result.name, "Transform");
assert!(result.children.is_some());
assert_eq!(result.children.as_ref().unwrap().len(), 3);
}
#[tokio::test]
async fn test_diff_components() {
let inspector = BevyReflectionInspector::new();
let old_value = json!({"x": 1.0, "y": 2.0});
let new_value = json!({"x": 1.5, "y": 2.0, "z": 3.0});
let diff_result = inspector.diff_components("TestComponent", &old_value, &new_value).await.unwrap();
assert_eq!(diff_result.type_name, "TestComponent");
assert!(diff_result.summary.changed_fields > 0);
assert!(diff_result.summary.added_fields > 0);
}
#[test]
fn test_change_severity_assessment() {
let inspector = BevyReflectionInspector::new();
let old_num = json!(10.0);
let new_num_trivial = json!(10.001);
let new_num_minor = json!(10.5);
let new_num_major = json!(15.0);
assert!(matches!(inspector.assess_change_severity(&old_num, &new_num_trivial), ChangeSeverity::Trivial));
assert!(matches!(inspector.assess_change_severity(&old_num, &new_num_minor), ChangeSeverity::Minor));
assert!(matches!(inspector.assess_change_severity(&old_num, &new_num_major), ChangeSeverity::Major));
}
}