mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-29 09:08:58 +00:00
Logic for inspector field muts (#73)
Allow to send change events from typescript to bevy
Example (test)
```rust
CHANGE_FROM_UI.write_message(UpdateField {
component: "bevy_transform::components::transform::Transform".to_string(),
reflect_path: ".translation.x".to_string(),
value: 0.to_string(),
});
```
Function for typescript
```rust
#[wasm_bindgen(js_name = changeValue)]
pub fn change_value(msg: String) {
let Ok(msg) = serde_json::from_str::<UpdateField>(&msg) else {
error!("Failed to parse message: {}", msg);
return;
};
CHANGE_FROM_UI.write_message(msg);
}
```
This commit is contained in:
+179
-3
@@ -1,4 +1,4 @@
|
|||||||
use std::marker::PhantomData;
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@@ -8,10 +8,19 @@ use bevy::{
|
|||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::*;
|
use serde_json::*;
|
||||||
use space_editor::space_prefab::component::MeshPrimitivePrefab;
|
use space_editor::{
|
||||||
|
space_editor_ui::{
|
||||||
|
editor_tab::{EditorTab, EditorTabName},
|
||||||
|
EditorUiAppExt,
|
||||||
|
},
|
||||||
|
space_prefab::component::MeshPrimitivePrefab,
|
||||||
|
};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::{interaction::Selected, wasm};
|
use crate::{
|
||||||
|
interaction::Selected,
|
||||||
|
wasm::{self, StaticBuffer, WasmMessageBuffer},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct InspectorPlugin;
|
pub struct InspectorPlugin;
|
||||||
|
|
||||||
@@ -23,6 +32,7 @@ impl Plugin for InspectorPlugin {
|
|||||||
app.configure_sets(
|
app.configure_sets(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
|
InspectorSystem::WriteChanges,
|
||||||
InspectorSystem::DetectChanges,
|
InspectorSystem::DetectChanges,
|
||||||
InspectorSystem::PerComponentCollect,
|
InspectorSystem::PerComponentCollect,
|
||||||
InspectorSystem::CollectChanges,
|
InspectorSystem::CollectChanges,
|
||||||
@@ -35,12 +45,32 @@ impl Plugin for InspectorPlugin {
|
|||||||
update_description.in_set(InspectorSystem::CollectChanges),
|
update_description.in_set(InspectorSystem::CollectChanges),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
update_field_by_ui.in_set(InspectorSystem::WriteChanges),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.editor_tab_by_trait(
|
||||||
|
EditorTabName::Other("Inspector tester".to_string()),
|
||||||
|
TestInspectorTab::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.init_resource::<InspectGetters>();
|
||||||
|
|
||||||
app.register_inspect::<Transform>();
|
app.register_inspect::<Transform>();
|
||||||
app.register_inspect::<Name>();
|
app.register_inspect::<Name>();
|
||||||
app.register_inspect::<MeshPrimitivePrefab>();
|
app.register_inspect::<MeshPrimitivePrefab>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct InspectGetters {
|
||||||
|
getters: HashMap<
|
||||||
|
String,
|
||||||
|
Arc<dyn Fn(&mut EntityWorldMut, &str, &dyn Fn(&mut dyn Reflect)) + Send + Sync>,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
trait AppInspectable {
|
trait AppInspectable {
|
||||||
fn register_inspect<T: Component + Reflect + GetTypeRegistration>(&mut self);
|
fn register_inspect<T: Component + Reflect + GetTypeRegistration>(&mut self);
|
||||||
}
|
}
|
||||||
@@ -62,11 +92,51 @@ impl AppInspectable for App {
|
|||||||
Update,
|
Update,
|
||||||
auto_serialize_system::<T>.in_set(InspectorSystem::PerComponentCollect),
|
auto_serialize_system::<T>.in_set(InspectorSystem::PerComponentCollect),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut getters = self.world.get_resource_mut::<InspectGetters>().unwrap();
|
||||||
|
getters.getters.insert(
|
||||||
|
T::get_type_registration()
|
||||||
|
.type_info()
|
||||||
|
.type_path()
|
||||||
|
.to_string(),
|
||||||
|
Arc::new(|entity, path, f| {
|
||||||
|
let mut component = entity.get_mut::<T>().unwrap();
|
||||||
|
let reflect_field = component.reflect_path_mut(path).unwrap();
|
||||||
|
(f)(reflect_field);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Resource)]
|
||||||
|
pub struct TestInspectorTab {}
|
||||||
|
|
||||||
|
impl EditorTab for TestInspectorTab {
|
||||||
|
fn ui(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut bevy_inspector_egui::egui::Ui,
|
||||||
|
_commands: &mut Commands,
|
||||||
|
_world: &mut World,
|
||||||
|
) {
|
||||||
|
if ui.button("Test set transform x to zero").clicked() {
|
||||||
|
CHANGE_FROM_UI.write_message(UpdateField {
|
||||||
|
component: "bevy_transform::components::transform::Transform".to_string(),
|
||||||
|
reflect_path: ".translation.x".to_string(),
|
||||||
|
value: serde_json::Value::from(0.0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn title(&self) -> bevy_inspector_egui::egui::WidgetText {
|
||||||
|
"Inspector tester".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(SystemSet, Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
#[derive(SystemSet, Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
pub enum InspectorSystem {
|
pub enum InspectorSystem {
|
||||||
|
WriteChanges,
|
||||||
DetectChanges,
|
DetectChanges,
|
||||||
PerComponentCollect,
|
PerComponentCollect,
|
||||||
CollectChanges,
|
CollectChanges,
|
||||||
@@ -88,6 +158,94 @@ impl<T> Default for UpdateInspect<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_field_by_ui(world: &mut World) {
|
||||||
|
unsafe {
|
||||||
|
let unsafe_world = world.as_unsafe_world_cell();
|
||||||
|
let Some(msg) = CHANGE_FROM_UI.take_message() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut selected_query = unsafe_world
|
||||||
|
.world_mut()
|
||||||
|
.query_filtered::<Entity, With<Selected>>();
|
||||||
|
let Ok(entity) = selected_query.get_single(unsafe_world.world_mut()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let getters = unsafe_world.get_resource_mut::<InspectGetters>().unwrap();
|
||||||
|
|
||||||
|
let mut entity_mut = unsafe_world.world_mut().entity_mut(entity);
|
||||||
|
|
||||||
|
let Some(getter) = getters.getters.get(&msg.component) else {
|
||||||
|
error!("Failed to find getter for component: {}", msg.component);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
getter.as_ref()(&mut entity_mut, &msg.reflect_path, &|reflect_field| {
|
||||||
|
if let Some(val) = reflect_field.downcast_mut::<f32>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<f64>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<u32>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<u64>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<i32>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<i64>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<bool>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<String>() {
|
||||||
|
*val = msg.value.as_str().unwrap().to_string();
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<Vec3>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else if let Some(val) = reflect_field.downcast_mut::<Quat>() {
|
||||||
|
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||||
|
error!("Failed to parse value: {}", msg.value);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*val = msg_value;
|
||||||
|
} else {
|
||||||
|
error!("Unsupported type {}!", &msg.reflect_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Resource, Serialize, Deserialize, Default, Clone)]
|
#[derive(Resource, Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct EntityDescription {
|
pub struct EntityDescription {
|
||||||
pub components: HashMap<String, Value>,
|
pub components: HashMap<String, Value>,
|
||||||
@@ -194,6 +352,13 @@ fn update_description(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct UpdateField {
|
||||||
|
pub component: String,
|
||||||
|
pub reflect_path: String,
|
||||||
|
pub value: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(typescript_custom_section)]
|
#[wasm_bindgen(typescript_custom_section)]
|
||||||
const EVENT_DECLARATION: &str = r#"
|
const EVENT_DECLARATION: &str = r#"
|
||||||
|
|
||||||
@@ -214,3 +379,14 @@ pub(super) fn notify_changed(q_window: &Window, msg: String) {
|
|||||||
error!("Failed to dispatch inspector-changed: {}", e);
|
error!("Failed to dispatch inspector-changed: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CHANGE_FROM_UI: WasmMessageBuffer<UpdateField> = WasmMessageBuffer::new();
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = changeValue)]
|
||||||
|
pub fn change_value(msg: JsValue) {
|
||||||
|
let Ok(msg) = serde_wasm_bindgen::from_value(msg) else {
|
||||||
|
error!("Failed to parse message");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
CHANGE_FROM_UI.write_message(msg);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user