diff --git a/studio-frontend/src/app/app.element.ts b/studio-frontend/src/app/app.element.ts index 0183907..e66c83f 100644 --- a/studio-frontend/src/app/app.element.ts +++ b/studio-frontend/src/app/app.element.ts @@ -231,16 +231,30 @@ export class AppElement extends LitElement { @space-change=${this.onTransformSpaceChange} > - + + +
+
+ ${this.sceneObjects.map(obj => html` + + `)} +
+
` : nothing} diff --git a/studio-frontend/src/app/inspector.element.ts b/studio-frontend/src/app/inspector.element.ts index dcf244a..b8063ad 100644 --- a/studio-frontend/src/app/inspector.element.ts +++ b/studio-frontend/src/app/inspector.element.ts @@ -2,20 +2,250 @@ import { on } from "@storyteller/framework"; import { type InspectorDomChanged } from "dist/studio/studio"; import { LitElement, html, unsafeCSS, nothing, HTMLTemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; +import { property } from "lodash"; + +//vdom example +/// { +///"subtree":0, +/// "dirty_scopes":[], +/// "templates":[{"name":"studio\\src\\ui\\inspector.rs:46:15:160899","roots":[{"type":"Element","tag":"div","namespace":null,"attrs":[],"children":[{"type":"Text","text":"Inspector"},{"type":"Element","tag":"div","namespace":null,"attrs":[],"children":[{"type":"Text","text":"hello world"}]}]}],"node_paths":[],"attr_paths":[]}], +/// "edits":[{"type":"LoadTemplate","name":"studio\\src\\ui\\inspector.rs:46:15:160899","index":0,"id":1},{"type":"AppendChildren","id":0,"m":1}]} + @customElement("sts-inspector") export class AppElement extends LitElement { @state() vdom?: string | null; + @state() generation = 0; + @state() vdom_elements?: VdomElement[] | null; - @on("window:inspector-dom-changed") - onInspectorDomChanged({ detail: state}: InspectorDomChanged): void { - console.log("inspector dom changed:", state); - } + // @on("window:inspector-dom-changed") + // onInspectorDomChanged({ detail: state}: InspectorDomChanged): void { + // console.log("inspector dom changed:", state); + // //change vdom and activate render + // this.vdom = null; + // this.vdom = state; + // this.generation += 1; - protected override render = () => html` -
+ // // Activate the render function + // this.requestUpdate(); + // } -
- `; + protected override render() { + this.shadowRoot + + return html` + + + +
+ ` + + //convert string vdom to json object + // if (this.vdom) { + // var vdom = JSON.parse(this.vdom); + + // var templates = vdom["templates"]; + // var edits = vdom["edits"]; + + // console.log("edits", edits); + // console.log("templates", templates); + + // var element_stack = []; + + // var element_tree = []; + + // //loop through edits + // for (const edit of edits) { + + // var type = edit["type"]; + // if (type == "LoadTemplate") { + // var index = edit["index"]; + // var template_name = edit["name"]; + // var id = edit["id"]; + + // var target_root = null; + // for (const template of templates) { + // if (template["name"] == template_name) { + // target_root = template["roots"][index]; + // break; + // } + // } + // if (target_root == null) { + // console.log("template not found:", template_name); + // continue; + // } else { + // target_root["id"] = id; + // element_stack.push(target_root); + // } + // } else if (type == "AppendChildren") { + // var id = edit["id"]; + // var count = edit["m"]; + // if (id == 0) { + // for (var i = 0; i < count; i++) { + // var current_element = element_stack.pop(); + // element_tree.push(current_element); + // } + // } else { + // //TODO + // } + // } else if (type == "HydrateText") { + // var child_path = edit["path"]; + // var target_node = element_stack[element_stack.length - 1]; + + // for (const path of child_path) { + // target_node = target_node["children"][path]; + // } + + // //add text node to target_node + // if (target_node["children"] == undefined) { + // target_node["children"] = []; + // } + // target_node["children"].push({type: "Text", text: edit["value"], id: edit["id"]}); + + + // } + // } + + // console.log("element tree", element_tree); + + // //convert any to VdomElement in element_tree + // var vdom_elements = element_tree.map((element) => { + // return element as VdomElement; + // }); + + // this.vdom_elements = vdom_elements; + + // return html` + + // + + //
+ // ${this.vdom_elements.map((element, index) => html``)} + //
` + + + // } else { + // //For each vdom element render InspectorElement + // return html` + + // ` + // } + + + } + +} + +export interface VdomElement { + id?: number; + type?: string; + text?: string; + tag?: string; + namespace?: string; + attrs?: any[]; + children?: VdomElement[]; +} + + +/// Element for recursive render vdom tree +@customElement("sts-inspector-element") +export class InspectorElement extends LitElement { + @property({ type: Object, attribute: true }) + vdom: VdomElement; + + @property({ type: Number, attribute: true }) + generation: number; + + protected override render() { + var tag = this.vdom["tag"]; + var type = this.vdom.type; + + if (type == "Text") { + return html`
${this.vdom["text"]}
` + } else if (type == "Dynamic") { + return html` +
+ ${this.vdom["children"].map((element) => html``)} +
+ ` + } + + if (tag == "div") { + return html` + + +
+ ${this.vdom["children"].map((element) => html``)} +
` + } else { + return html`
${this.vdom["text"]}
` + } + } } \ No newline at end of file diff --git a/studio-frontend/src/dioxus-machine.ts b/studio-frontend/src/dioxus-machine.ts new file mode 100644 index 0000000..f02fb0c --- /dev/null +++ b/studio-frontend/src/dioxus-machine.ts @@ -0,0 +1,226 @@ +import { type InspectorDomChanged } from "dist/studio/studio"; + +console.log("Hello, World!"); + + +function load_child(stack : any[], child_path : number[]) { + // iterate through each number and get that child + var node : any = stack[stack.length - 1]; + for (const ptr of child_path) { + var steps = ptr; + var m = 0; + for (node = node.firstChild; m < steps; m++) { + node = node.nextSibling; + } + } + return node; +} + +function laod_template(edit : any, stack : any[]) { + +} + +function apply_muts( + event : InspectorDomChanged +) { + + if (event.detail !== null) { + + var lit_root = document.getElementById("inspector"); + var shadowroot = lit_root?.shadowRoot; + var root = shadowroot?.getElementById("inspector"); + console.log("root", root); + if (root) { + //remove all children + while (root.firstChild) { + root.removeChild(root.firstChild); + } + } else { + console.log("root not found"); + return; + } + + var vdom = JSON.parse(event.detail); + var templates = vdom["templates"]; + var edits = vdom["edits"]; + + console.log("edits", edits); + console.log("templates", templates); + + var element_stack = []; + var nodes : any = {} + + + + nodes[0] = document.createElement("div"); + root.appendChild(nodes[0]); + element_stack.push(root); + + //loop through edits + for (const edit of edits) { + + var type = edit["type"]; + if (type == "LoadTemplate") { + // var index = edit["index"]; + // var template_name = edit["name"]; + // var id = edit["id"]; + + // var target_root = null; + // for (const template of templates) { + // if (template["name"] == template_name) { + // target_root = template["roots"][index]; + // break; + // } + // } + // if (target_root == null) { + // console.log("template not found:", template_name); + // continue; + // } else { + // target_root["id"] = id; + // element_stack.push(target_root); + // } + + var id = edit["id"]; + var index = edit["index"]; + var template_name = edit["name"]; + + var template_node = null; + for (const template of templates) { + if (template["name"] == template_name) { + template_node = template["roots"][index]; + break; + } + } + + if (template_node == null) { + console.log("template not found:", template_name); + continue; + } else { + var node = recursive_render(null, template_node); + nodes[id] = node; + element_stack.push(node); + } + + } else if (type == "AppendChildren") { + // var id = edit["id"]; + // var count = edit["m"]; + // if (id == 0) { + // for (var i = 0; i < count; i++) { + // var current_element = element_stack.pop(); + // element_tree.push(current_element); + // } + // } else { + // //TODO + // } + + var id = edit["id"]; + var many = edit["m"]; + + var target_node = nodes[id]; + for (var i = 0; i < many; i++) { + var current_element = element_stack.pop(); + target_node.appendChild(current_element); + } + } else if (type == "HydrateText") { + // var child_path = edit["path"]; + // var target_node = element_stack[element_stack.length - 1]; + + // for (const path of child_path) { + // target_node = target_node["children"][path]; + // } + + // if (target_node == null) { + // console.log("target_node not found:", child_path); + // continue; + // } + + // //add text node to target_node + // if (target_node["children"] == undefined) { + // target_node["children"] = []; + // } + // target_node["children"].push({type: "Text", text: edit["value"], id: edit["id"]}); + + var node : HTMLElement = load_child(element_stack, edit["path"]); + if (node) { + node.textContent = edit["value"]; + } else { + console.log("node not found:", edit["path"]); + } + } else if (type == "ReplacePlaceholder") { + var child_path = edit["path"]; + var manu = edit["m"]; + + var els = []; + for (var i = 0; i < manu; i++) { + var el = element_stack.pop(); + els.push(el); + } + var target_node = load_child(element_stack, child_path); + target_node.replaceWith(...els); + console.log("") + } + } + + + } else { + console.log('Selection has been cleared.'); + } +} + +document.addEventListener('inspector-dom-changed', (event: CustomEvent) => { + // Cast the event to the InspectorDomChanged type to access its details in TypeScript + const customEvent = event as InspectorDomChanged; + + // Now you can access the detail property, which is either a string or null + apply_muts(customEvent); +}); + + +export interface VdomElement { + id?: number; + type?: string; + text?: string; + tag?: string; + namespace?: string; + attrs?: any[]; + children?: VdomElement[]; +} + +function recursive_render( + root: HTMLElement | null, + element: VdomElement, +) { + //create element + var tag = element["tag"]; + if (element.type == "Text") { + tag = "div"; + } else if (element.type == "Dynamic") { + tag = "div"; + } + + var new_element = document.createElement(element["tag"] as string); + + //set text + if (element.text != undefined) { + new_element.textContent = element.text; + } + + //set id + if (element.id != undefined) { + new_element.id = element.id.toString(); + } + + //parent to root + if (root != null) { + root.appendChild(new_element); + } + + //render children + if (element["children"] != undefined) { + for (const child of element["children"]) { + recursive_render(new_element, child); + } + } + + return new_element; +} \ No newline at end of file diff --git a/studio-frontend/src/main.ts b/studio-frontend/src/main.ts index cde5d45..538a170 100644 --- a/studio-frontend/src/main.ts +++ b/studio-frontend/src/main.ts @@ -1 +1,2 @@ import "./app/app.element"; +import "./dioxus-machine"; diff --git a/studio/Cargo.toml b/studio/Cargo.toml index 7aac79c..898752d 100644 --- a/studio/Cargo.toml +++ b/studio/Cargo.toml @@ -41,6 +41,9 @@ space_editor = {git = "https://github.com/rewin123/space_editor", rev = "ce83f86 anyhow.workspace = true dioxus = "0.4.3" futures-util = "0.3.30" +sledgehammer_bindgen = { version = "0.3.1", default-features = false, optional = true } +sledgehammer_utils = { version = "0.2", optional = true } +pretty-type-name = "1.0.1" [features] wasm = [ @@ -51,6 +54,8 @@ wasm = [ "dep:serde-wasm-bindgen", "dep:web-sys", "bevy_rapier3d/wasm-bindgen", + "dep:sledgehammer_bindgen", + "dep:sledgehammer_utils", ] desktop = [] diff --git a/studio/inspector.ts b/studio/inspector.ts new file mode 100644 index 0000000..0627df9 --- /dev/null +++ b/studio/inspector.ts @@ -0,0 +1,10 @@ + +console.log("Hello, World!"); + +var root; +var node_stack = []; + +export function start() { + root = document.getElementById("inspector"); + console.log("root", root); +} \ No newline at end of file diff --git a/studio/scripts/copy-assets.mjs b/studio/scripts/copy-assets.mjs index 17a0592..e950bac 100644 --- a/studio/scripts/copy-assets.mjs +++ b/studio/scripts/copy-assets.mjs @@ -16,6 +16,9 @@ export default async function main() { let count = 0; const start = performance.now(); + //copy inspector.ts to dist + fs.copyFileSync(path.join(__dirname, "../inspector.ts"), path.join(__dirname, "../../dist/studio/inspector.ts")); + try { /** @type string[] */ let ignore = []; diff --git a/studio/src/ui/inspector.rs b/studio/src/ui/inspector.rs index 8df8085..32799b7 100644 --- a/studio/src/ui/inspector.rs +++ b/studio/src/ui/inspector.rs @@ -1,193 +1,192 @@ use std::sync::{Arc, Mutex, RwLock}; -use bevy::{prelude::*, utils::hashbrown::HashMap, window::PrimaryWindow}; -use dioxus::{core::{ElementId, Mutations}, prelude::*}; +use bevy::{ecs::world::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}, prelude::*, reflect::TypeData, utils::hashbrown::HashMap, window::PrimaryWindow}; +use dioxus::{prelude::*, rsx::*}; +use serde::{Deserialize, Serialize}; +use space_editor::{prelude::Selected, space_editor_ui::inspector::inspect}; use wasm_bindgen::JsCast; use web_sys::HtmlElement; use crate::{ui::inspector::wasm::notify_dom_changed, wasm::{StaticBuffer, WasmMessageBuffer}}; +use super::utils::{ComponentProp, InspectedEntity}; + pub struct InspectorPlugin; impl Plugin for InspectorPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, init); - app.add_systems(Update, apply_muts); + app.init_resource::(); + + app.add_systems(Update, show_selected); + + app.world.resource_mut::().add_simple::(); + app.world.resource_mut::().add_simple::(); } } -struct InspectorDom { - dom: VirtualDom, +#[derive(Resource, Default)] +struct InspectorFunctions { + get_reflects: Vec) -> Option> + Send + Sync>> } -fn init( - world: &mut World, -) { - let mut dom = VirtualDom::new(inspector); - - world.insert_non_send_resource(InspectorDom { dom }); - - // let muts = dom.rebuild(); - - // info!("{:?}", muts); - - // let json = serde_json::to_string(&muts).unwrap(); - - // q_mutation_queue.edits.push(json.clone()); - - // info!("{:?}", json); - - // #[cfg(feature = "wasm")] - // { - // wasm::MUTATION_BUFFER.write_message(json); - // notify_dom_changed(q_window); -} - -fn recursive_find( - id : &str, - element: &web_sys::Node, -) -> Option { - if let Some(el) = element.as_any().downcast_ref::() { - if el.id() == *id { - return Some(el.clone()); - } - } - let children = element.child_nodes(); - - for i in 0..children.length() { - if let Some(child) = children.get(i) { - if let Some(element) = recursive_find(id, &child) { - return Some(element); - } - } +impl InspectorFunctions { + fn add_getter) -> Option> + Send + Sync + 'static>(&mut self, f: F) { + self.get_reflects.push(Box::new(f)); } - None -} + fn add_simple(&mut self) where T: bevy::prelude::Component + Reflect + FromReflect + 'static { -fn apply_muts( - mut dom : NonSendMut, -) { - if let Some(window) = web_sys::window() { - window.capture_events(); - window.release_events(); - info!("apply_muts 1"); - if let Some(document) = window.document() { - - info!("apply_muts 2"); - - if let Some(mut page_root) = document.get_elements_by_name("sts-inspector").item(0) { - let mut page_root = page_root.dyn_into::().unwrap(); - info!("apply_muts 3"); - - let dom = &mut dom.dom; - let muts = dom.render_immediate(); - - let mut element_stack = vec![]; - - for m in muts.edits.iter() { - match m { - dioxus::core::Mutation::AppendChildren { id, m } => { - for stack_idx in 0..*m { - let (id, template) = element_stack.pop().unwrap(); - recursive_add_dom( - &id, - &template, - &mut page_root, - &document, - ); - } - }, - dioxus::core::Mutation::AssignId { path, id } => todo!(), - dioxus::core::Mutation::CreatePlaceholder { id } => todo!(), - dioxus::core::Mutation::CreateTextNode { value, id } => todo!(), - dioxus::core::Mutation::HydrateText { path, value, id } => todo!(), - dioxus::core::Mutation::LoadTemplate { name, index, id } => { - let mut template = None; - for t in muts.templates.iter() { - if t.name == *name { - template = Some(t); - break; - } - } - - if let Some(template) = template { - element_stack.push((id.clone(), template.roots[*index].clone())); - } - }, - dioxus::core::Mutation::ReplaceWith { id, m } => todo!(), - dioxus::core::Mutation::ReplacePlaceholder { path, m } => todo!(), - dioxus::core::Mutation::InsertAfter { id, m } => todo!(), - dioxus::core::Mutation::InsertBefore { id, m } => todo!(), - dioxus::core::Mutation::SetAttribute { name, value, id, ns } => todo!(), - dioxus::core::Mutation::SetText { value, id } => todo!(), - dioxus::core::Mutation::NewEventListener { name, id } => todo!(), - dioxus::core::Mutation::RemoveEventListener { name, id } => todo!(), - dioxus::core::Mutation::Remove { id } => todo!(), - dioxus::core::Mutation::PushRoot { id } => todo!(), + unsafe { + self.add_getter( + |cell| { + if let Some(val) = cell.get::() { + Some(Box::new(::from_reflect(val).unwrap()) as Box) + } else { + None } } + ) + } + } + + fn get_reflect_copy(&mut self, cell: &UnsafeEntityCell<'_>) -> Vec> { + self.get_reflects.iter_mut().map(|f| (*f)(cell)).flatten().collect() + } +} + + +fn web_inspector(cx: Scope) -> dioxus::core::Element { + + let e = cx.props; + + let components = e.components.iter().map(|c: &Box| ComponentProp { component: c.clone_value() }).collect::>(); + let components_rendered = components.iter().map(|c : &ComponentProp| { + rsx! { + web_reflect { component: c.component.clone_value() } + } + }); + + if e.id.is_none() { + render! { + div { + "No entity selected" + } + } + } else { + let id_name = format!("{:?}", e.id.unwrap()); + render! { + div { + id_name, + components_rendered } } } } -fn recursive_add_dom( - element_id: &ElementId, - template: &TemplateNode, - parent: &mut web_sys::Element, - document: &web_sys::Document, -) { - match *template { - TemplateNode::Element { tag, namespace, attrs, children } => { - let mut element = document.create_element(tag).unwrap(); - element.set_attribute("id", format!("element_{}", element_id.0).as_str()).unwrap(); - - for attr in attrs { - match *attr { - TemplateAttribute::Static { name, value, namespace } => { - element.set_attribute(name, value).unwrap(); - }, - TemplateAttribute::Dynamic { id } => { - // TODO - }, +fn web_reflect(cx: Scope) -> dioxus::core::Element { + let res = match cx.props.component.reflect_ref() { + + bevy::reflect::ReflectRef::Struct(v) => { + let field_count = v.field_len(); + render! { + div { + v.type_name().split("::").last().unwrap(), + (0..field_count).map(|i| { + let field = v.field_at(i).unwrap(); + render! { + web_reflect { component: field.clone_value() } + + } + }) + } } + }, + bevy::reflect::ReflectRef::TupleStruct(_) => { + render! { + div { - parent.append_child(&element).unwrap(); - - for child in children { - recursive_add_dom(element_id, child, &mut element, document); + } } }, - TemplateNode::Text { text } => { - let text_node = document.create_element("p").unwrap(); - text_node.set_text_content(Some(text)); - parent.append_child(&text_node).unwrap(); + bevy::reflect::ReflectRef::Tuple(_) => { + render! { + div { + + } + } }, - TemplateNode::Dynamic { id } => { - todo!() + bevy::reflect::ReflectRef::List(_) => { + render! { + div { + + } + } }, - TemplateNode::DynamicText { id } => { - todo!() + bevy::reflect::ReflectRef::Array(_) => { + render! { + div { + + } + } }, + bevy::reflect::ReflectRef::Map(_) => { + render! { + div { + + } + } + }, + bevy::reflect::ReflectRef::Enum(_) => { + render! { + div { + + } + } + }, + bevy::reflect::ReflectRef::Value(_) => { + render! { + div { + + } + } + }, + }; + return res; +} + + +fn show_selected( + world: &mut World +) { + unsafe { + let cell = world.as_unsafe_world_cell(); + + let Ok(selected_entity) = cell.world_mut().query_filtered::>().get_single(cell.world()) else { + return; + }; + + let mut functions = cell.get_resource_mut::().unwrap(); + + let e = cell.get_entity(selected_entity).unwrap(); + + let mut cell = InspectedEntity::none(); + cell.id = Some(e.id()); + cell.components = functions.get_reflect_copy(&e); + + + let mut dom = VirtualDom::new_with_props(web_inspector, cell); + + let muts = dom.rebuild(); + + let q_window = world.query_filtered::<&Window, With>().single(world); + + let _ = notify_dom_changed(q_window, serde_json::to_string(&muts).unwrap()); + } } -fn inspector( - cx: Scope -) -> Element { - - cx.render(rsx!{ - div { - "Inspector", - div { - "hello world" - } - } - }) -} #[cfg(feature = "wasm")] @@ -218,10 +217,11 @@ declare global { "#; pub(super) fn notify_dom_changed( - q_window: Query<&Window, With>, + q_window: &Window, + msg : String, ) -> Result<(), String> { - wasm::dispatch_to_js(q_window.single(), "inspector-dom-changed", &Option::::None) + wasm::dispatch_to_js(q_window, "inspector-dom-changed", &msg) } pub static MUTATION_BUFFER: WasmMessageBuffer = WasmMessageBuffer::new(); @@ -232,4 +232,4 @@ declare global { MUTATION_BUFFER.take_message().unwrap_or("{}".to_string()) } -} \ No newline at end of file +} diff --git a/studio/src/ui/mod.rs b/studio/src/ui/mod.rs index 32a9ef7..1e853c3 100644 --- a/studio/src/ui/mod.rs +++ b/studio/src/ui/mod.rs @@ -4,6 +4,7 @@ use self::fps::FpsCounterPlugin; mod fps; mod inspector; +mod utils; pub struct UiLayerPlugin; diff --git a/studio/src/ui/utils.rs b/studio/src/ui/utils.rs new file mode 100644 index 0000000..eaeab26 --- /dev/null +++ b/studio/src/ui/utils.rs @@ -0,0 +1,193 @@ + +use bevy::prelude::*; +use dioxus::prelude::*; + +#[derive(Props)] +pub struct InspectedEntity { + pub id: Option, + pub components: Vec>, +} + +#[derive(Props)] +pub struct ComponentProp { + pub component: Box, +} + +impl Clone for ComponentProp { + fn clone(&self) -> Self { + Self { + component: self.component.clone_value() + } + } +} + +impl<'a> PartialEq for ComponentProp { + fn eq(&self, other: &Self) -> bool { + recursive_reflect_eq(self.component.as_ref(), other.component.as_ref()) + } +} + +pub fn recursive_reflect_eq(a : &dyn Reflect, b : &dyn Reflect) -> bool { + let ref_a = a.reflect_ref(); + let ref_b = b.reflect_ref(); + + match ref_a { + bevy::reflect::ReflectRef::Struct(a) => { + let bevy::reflect::ReflectRef::Struct(b) = b.reflect_ref() else { + return false; + }; + + if a.field_len() != b.field_len() { + return false; + } + + for i in 0..a.field_len() { + if !recursive_reflect_eq(a.field_at(i).unwrap(), b.field_at(i).unwrap()) { + return false; + } + } + }, + bevy::reflect::ReflectRef::TupleStruct(a) => { + let bevy::reflect::ReflectRef::TupleStruct(b) = b.reflect_ref() else { + return false; + }; + + if a.field_len() != b.field_len() { + return false; + } + + for i in 0..a.field_len() { + if !recursive_reflect_eq(a.field(i).unwrap(), b.field(i).unwrap()) { + return false; + } + } + }, + bevy::reflect::ReflectRef::Tuple(a) => { + let bevy::reflect::ReflectRef::Tuple(b) = b.reflect_ref() else { + return false; + }; + + if a.field_len() != b.field_len() { + return false; + } + + for i in 0..a.field_len() { + if !recursive_reflect_eq(a.field(i).unwrap(), b.field(i).unwrap()) { + return false; + } + } + }, + bevy::reflect::ReflectRef::List(a) => { + let bevy::reflect::ReflectRef::List(b) = b.reflect_ref() else { + return false; + }; + + if a.len() != b.len() { + return false; + } + + for i in 0..a.len() { + if !recursive_reflect_eq(a.get(i).unwrap(), b.get(i).unwrap()) { + return false; + } + } + }, + bevy::reflect::ReflectRef::Array(a) => { + let bevy::reflect::ReflectRef::Array(b) = b.reflect_ref() else { + return false; + }; + + if a.len() != b.len() { + return false; + } + + for i in 0..a.len() { + if !recursive_reflect_eq(a.get(i).unwrap(), b.get(i).unwrap()) { + return false; + } + } + }, + bevy::reflect::ReflectRef::Map(a) => { + let bevy::reflect::ReflectRef::Map(b) = b.reflect_ref() else { + return false; + }; + + if a.len() != b.len() { + return false; + }; + + for i in 0..a.len() { + let (a_key, a_value) = a.get_at(i).unwrap(); + let Some(b_value) = b.get(a_key) else { + return false; + }; + + if !recursive_reflect_eq(a_value, b_value) { + return false; + } + } + }, + bevy::reflect::ReflectRef::Enum(a) => { + let bevy::reflect::ReflectRef::Enum(b) = b.reflect_ref() else { + return false; + }; + + if a.variant_name() != b.variant_name() { + return false; + } + + if a.field_len() != b.field_len() { + return false; + } + + for i in 0..a.field_len() { + if !recursive_reflect_eq(a.field_at(i).unwrap(), b.field_at(i).unwrap()) { + return false; + } + } + }, + bevy::reflect::ReflectRef::Value(a) => { + let bevy::reflect::ReflectRef::Value(b) = b.reflect_ref() else { + return false; + }; + + if let Some(res) = a.reflect_partial_eq(b) { + return res; + } else { + return false; + } + }, + } + + false +} + +impl PartialEq for InspectedEntity { + fn eq(&self, other: &Self) -> bool { + if self.id != other.id { + return false; + } + + if self.components.len() != other.components.len() { + return false; + } + + for i in 0..self.components.len() { + + if !recursive_reflect_eq(self.components[i].as_ref(), other.components[i].as_ref()) { + return false; + } + } + + true + } +} + +impl InspectedEntity { + pub fn none() -> Self { + Self { + id: None, + components: Vec::new(), + } + } +} \ No newline at end of file