use std::any::TypeId;
use bevy::{
ecs::{
component::ComponentId,
reflect::{AppTypeRegistry, ReflectComponent},
},
prelude::*,
};
use serde::de::DeserializeSeed;
pub use jackdaw_commands::{CommandGroup, CommandHistory, EditorCommand};
use crate::EditorEntity;
use crate::selection::{Selected, Selection};
pub struct CommandHistoryPlugin;
impl Plugin for CommandHistoryPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(CommandHistory::default());
}
}
pub struct SetComponentField {
pub entity: Entity,
pub component_type_id: TypeId,
pub field_path: String,
pub old_value: Box<dyn PartialReflect>,
pub new_value: Box<dyn PartialReflect>,
}
impl EditorCommand for SetComponentField {
fn execute(&mut self, world: &mut World) {
apply_reflected_value(
world,
self.entity,
self.component_type_id,
&self.field_path,
&*self.new_value,
);
}
fn undo(&mut self, world: &mut World) {
apply_reflected_value(
world,
self.entity,
self.component_type_id,
&self.field_path,
&*self.old_value,
);
}
fn description(&self) -> &str {
"Set component field"
}
}
fn apply_reflected_value(
world: &mut World,
entity: Entity,
component_type_id: TypeId,
field_path: &str,
value: &dyn PartialReflect,
) {
let registry = world.resource::<AppTypeRegistry>().clone();
let registry = registry.read();
let Some(registration) = registry.get(component_type_id) else {
return;
};
let Some(reflect_component) = registration.data::<ReflectComponent>() else {
return;
};
let Some(reflected) = reflect_component.reflect_mut(world.entity_mut(entity)) else {
return;
};
if field_path.is_empty() {
reflected.into_inner().apply(value);
} else {
let Ok(field) = reflected.into_inner().reflect_path_mut(field_path) else {
return;
};
field.apply(value);
}
}
pub struct SetTransform {
pub entity: Entity,
pub old_transform: Transform,
pub new_transform: Transform,
}
impl EditorCommand for SetTransform {
fn execute(&mut self, world: &mut World) {
if let Some(mut transform) = world.get_mut::<Transform>(self.entity) {
*transform = self.new_transform;
}
sync_component_to_ast::<Transform>(
world,
self.entity,
"bevy_transform::components::transform::Transform",
&self.new_transform,
);
}
fn undo(&mut self, world: &mut World) {
if let Some(mut transform) = world.get_mut::<Transform>(self.entity) {
*transform = self.old_transform;
}
sync_component_to_ast::<Transform>(
world,
self.entity,
"bevy_transform::components::transform::Transform",
&self.old_transform,
);
}
fn description(&self) -> &str {
"Set transform"
}
}
pub struct ReparentEntity {
pub entity: Entity,
pub old_parent: Option<Entity>,
pub new_parent: Option<Entity>,
}
impl EditorCommand for ReparentEntity {
fn execute(&mut self, world: &mut World) {
set_parent(world, self.entity, self.new_parent);
}
fn undo(&mut self, world: &mut World) {
set_parent(world, self.entity, self.old_parent);
}
fn description(&self) -> &str {
"Reparent entity"
}
}
fn set_parent(world: &mut World, entity: Entity, parent: Option<Entity>) {
let current_world = world.get::<GlobalTransform>(entity).copied();
let new_parent_world = parent.and_then(|p| world.get::<GlobalTransform>(p).copied());
match parent {
Some(p) => {
world.entity_mut(entity).insert(ChildOf(p));
}
None => {
world.entity_mut(entity).remove::<ChildOf>();
}
}
let new_transform =
if let (Some(world_tf), Some(parent_world)) = (current_world, new_parent_world) {
Some(Transform::from_matrix(
(parent_world.affine().inverse() * world_tf.affine()).into(),
))
} else if parent.is_none() {
current_world.map(|w| Transform::from_matrix(w.affine().into()))
} else {
None
};
if let Some(new_tf) = new_transform
&& let Some(mut tf) = world.get_mut::<Transform>(entity)
{
*tf = new_tf;
}
let parent_idx = {
let ast = world.resource::<jackdaw_jsn::SceneJsnAst>();
parent.and_then(|p| ast.ecs_to_jsn.get(&p).copied())
};
{
let mut ast = world.resource_mut::<jackdaw_jsn::SceneJsnAst>();
if let Some(node) = ast.node_for_entity_mut(entity) {
node.parent = parent_idx;
}
}
if let Some(new_tf) = new_transform {
sync_component_to_ast(
world,
entity,
"bevy_transform::components::transform::Transform",
&new_tf,
);
}
}
pub struct AddComponent {
pub entity: Entity,
pub type_id: TypeId,
pub component_id: ComponentId,
pub type_path: String,
promoted_components: Vec<String>,
}
impl AddComponent {
pub fn new(
entity: Entity,
type_id: TypeId,
component_id: ComponentId,
type_path: String,
) -> Self {
Self {
entity,
type_id,
component_id,
type_path,
promoted_components: Vec::new(),
}
}
}
impl EditorCommand for AddComponent {
fn execute(&mut self, world: &mut World) {
info!(
"AddComponent::execute entered: type_path={}, type_id={:?}, component_id={:?}, entity={:?}",
self.type_path, self.type_id, self.component_id, self.entity
);
let registry = world.resource::<AppTypeRegistry>().clone();
let registry = registry.read();
let Some(registration) = registry.get(self.type_id) else {
warn!(
"AddComponent::execute: registry has no entry for type_id {:?} (type_path={})",
self.type_id, self.type_path
);
return;
};
let Some(default_value) =
crate::reflect_default::build_reflective_default(self.type_id, ®istry)
else {
warn!(
"AddComponent::execute: type {} has no `ReflectDefault` and a field is an \
opaque type, list, map, or set with no default. Add `Default` to derives \
and `#[reflect(...)]`, or simplify the fields to reflected primitives.",
self.type_path
);
return;
};
let Some(reflect_component) = registration.data::<ReflectComponent>() else {
warn!(
"AddComponent::execute: type {} has no ReflectComponent. Add `Component` \
to `#[reflect(...)]`.",
self.type_path
);
return;
};
info!(
"AddComponent: inserting `{}` (type_id {:?}, component_id {:?}) on entity {:?}",
self.type_path, self.type_id, self.component_id, self.entity
);
reflect_component.insert(
&mut world.entity_mut(self.entity),
default_value.as_partial_reflect(),
®istry,
);
let has_after = world
.get_entity(self.entity)
.ok()
.map(|e| e.archetype().components().contains(&self.component_id))
.unwrap_or(false);
info!(
"AddComponent: post-insert, entity {:?} has component_id {:?}: {has_after}",
self.entity, self.component_id
);
let serializer =
bevy::reflect::serde::TypedReflectSerializer::new(default_value.as_ref(), ®istry);
match serde_json::to_value(&serializer) {
Ok(json_value) => {
drop(registry);
let mut ast = world.resource_mut::<jackdaw_jsn::SceneJsnAst>();
let entity_in_ast = ast.contains_entity(self.entity);
ast.set_component(self.entity, &self.type_path, json_value);
if !entity_in_ast {
warn!(
"AddComponent: entity {:?} is not tracked in the scene AST; \
{} is on the entity but won't persist through save/load.",
self.entity, self.type_path
);
}
}
Err(err) => {
warn!(
"AddComponent: failed to serialize default `{}` for AST: {err}. \
Component is on the entity but the AST doesn't track it.",
self.type_path
);
}
}
self.promoted_components = sync_required_to_ast(world, self.entity);
}
fn undo(&mut self, world: &mut World) {
let registry = world.resource::<AppTypeRegistry>().clone();
let reg = registry.read();
let promoted_component_ids: Vec<bevy::ecs::component::ComponentId> = self
.promoted_components
.iter()
.filter_map(|type_path| {
let type_id = reg.get_with_type_path(type_path)?.type_id();
world.components().get_id(type_id)
})
.collect();
drop(reg);
if let Ok(mut entity) = world.get_entity_mut(self.entity) {
entity.remove_by_id(self.component_id);
for cid in &promoted_component_ids {
entity.remove_by_id(*cid);
}
}
if let Some(node) = world
.resource_mut::<jackdaw_jsn::SceneJsnAst>()
.node_for_entity_mut(self.entity)
{
node.components.remove(&self.type_path);
node.derived_components.remove(&self.type_path);
for promoted in &self.promoted_components {
node.components.remove(promoted);
node.derived_components.remove(promoted);
}
}
if let Ok(mut ec) = world.get_entity_mut(self.entity) {
ec.insert(crate::inspector::InspectorDirty);
}
}
fn description(&self) -> &str {
"Add component"
}
}
pub struct RemoveComponent {
pub entity: Entity,
pub type_id: TypeId,
pub component_id: ComponentId,
pub type_path: String,
pub snapshot: Box<dyn PartialReflect>,
pub ast_snapshot: Option<serde_json::Value>,
}
impl EditorCommand for RemoveComponent {
fn execute(&mut self, world: &mut World) {
self.ast_snapshot = world
.resource::<jackdaw_jsn::SceneJsnAst>()
.get_component(self.entity, &self.type_path)
.cloned();
if let Ok(mut entity) = world.get_entity_mut(self.entity) {
entity.remove_by_id(self.component_id);
}
if let Some(node) = world
.resource_mut::<jackdaw_jsn::SceneJsnAst>()
.node_for_entity_mut(self.entity)
{
node.components.remove(&self.type_path);
}
}
fn undo(&mut self, world: &mut World) {
let registry = world.resource::<AppTypeRegistry>().clone();
let registry = registry.read();
let Some(registration) = registry.get(self.type_id) else {
return;
};
let Some(reflect_component) = registration.data::<ReflectComponent>() else {
return;
};
reflect_component.insert(
&mut world.entity_mut(self.entity),
&*self.snapshot,
®istry,
);
drop(registry);
if let Some(json_value) = self.ast_snapshot.take() {
world
.resource_mut::<jackdaw_jsn::SceneJsnAst>()
.set_component(self.entity, &self.type_path, json_value);
}
}
fn description(&self) -> &str {
"Remove component"
}
}
pub struct SpawnEntity {
pub spawned: Option<Entity>,
pub spawn_fn: Box<dyn Fn(&mut World) -> Entity + Send + Sync>,
pub label: String,
}
impl EditorCommand for SpawnEntity {
fn execute(&mut self, world: &mut World) {
let entity = (self.spawn_fn)(world);
self.spawned = Some(entity);
}
fn undo(&mut self, world: &mut World) {
if let Some(entity) = self.spawned.take() {
deselect_entities(world, &[entity]);
world
.resource_mut::<jackdaw_jsn::SceneJsnAst>()
.remove_node(entity);
if let Ok(entity_mut) = world.get_entity_mut(entity) {
entity_mut.despawn();
}
}
}
fn description(&self) -> &str {
&self.label
}
}
pub struct DespawnEntity {
pub entity: Entity,
pub scene_snapshot: DynamicScene,
pub parent: Option<Entity>,
pub label: String,
}
impl DespawnEntity {
pub fn from_world(world: &World, entity: Entity) -> Self {
let parent = world.get::<ChildOf>(entity).map(|c| c.0);
let scene = snapshot_entity(world, entity);
Self {
entity,
scene_snapshot: scene,
parent,
label: format!("Despawn entity {entity}"),
}
}
}
impl EditorCommand for DespawnEntity {
fn execute(&mut self, world: &mut World) {
deselect_entities(world, &[self.entity]);
world
.resource_mut::<jackdaw_jsn::SceneJsnAst>()
.remove_node(self.entity);
if let Ok(entity_mut) = world.get_entity_mut(self.entity) {
entity_mut.despawn();
}
}
fn undo(&mut self, world: &mut World) {
let scene = snapshot_rebuild(&self.scene_snapshot);
let mut entity_map = bevy::ecs::entity::hash_map::EntityHashMap::default();
let _ = scene.write_to_world(world, &mut entity_map);
if let Some(&new_id) = entity_map.get(&self.entity) {
self.entity = new_id;
}
crate::scene_io::register_entity_in_ast(world, self.entity);
}
fn description(&self) -> &str {
&self.label
}
}
pub(crate) fn filtered_scene_builder(world: &World) -> DynamicSceneBuilder<'_> {
DynamicSceneBuilder::from_world(world)
.deny_component::<Children>()
.deny_component::<GlobalTransform>()
.deny_component::<InheritedVisibility>()
.deny_component::<ViewVisibility>()
}
pub(crate) fn deselect_entities(world: &mut World, entities: &[Entity]) {
for &entity in entities {
if let Ok(mut ec) = world.get_entity_mut(entity) {
ec.remove::<Selected>();
}
}
let mut selection = world.resource_mut::<Selection>();
selection.entities.retain(|e| !entities.contains(e));
}
pub(crate) fn snapshot_entity(world: &World, entity: Entity) -> DynamicScene {
let mut entities = Vec::new();
collect_entity_ids(world, entity, &mut entities);
filtered_scene_builder(world)
.extract_entities(entities.into_iter())
.build()
}
pub(crate) fn collect_entity_ids(world: &World, entity: Entity, out: &mut Vec<Entity>) {
out.push(entity);
if let Some(children) = world.get::<Children>(entity) {
for child in children.iter() {
if world.get::<EditorEntity>(child).is_some()
|| world.get::<crate::NonSerializable>(child).is_some()
{
continue;
}
collect_entity_ids(world, child, out);
}
}
}
pub(crate) fn snapshot_rebuild(scene: &DynamicScene) -> DynamicScene {
DynamicScene {
resources: scene.resources.iter().map(|r| r.to_dynamic()).collect(),
entities: scene
.entities
.iter()
.map(|e| bevy::scene::DynamicEntity {
entity: e.entity,
components: e.components.iter().map(|c| c.to_dynamic()).collect(),
})
.collect(),
}
}
pub struct SetJsnField {
pub entity: Entity,
pub type_path: String,
pub field_path: String,
pub old_value: serde_json::Value,
pub new_value: serde_json::Value,
pub was_derived: bool,
}
impl EditorCommand for SetJsnField {
fn execute(&mut self, world: &mut World) {
{
let registry = world.resource::<AppTypeRegistry>().clone();
let registry = registry.read();
let mut ast = world.resource_mut::<jackdaw_jsn::SceneJsnAst>();
ast.set_component_field(
self.entity,
&self.type_path,
&self.field_path,
self.new_value.clone(),
®istry,
);
if let Some(node) = ast.node_for_entity_mut(self.entity)
&& node.derived_components.remove(&self.type_path)
{
self.was_derived = true;
info!(
"Promoted derived component '{}' to authored (user edited it)",
self.type_path
);
}
}
apply_jsn_field_to_ecs(
world,
self.entity,
&self.type_path,
&self.field_path,
&self.new_value,
);
}
fn undo(&mut self, world: &mut World) {
{
let registry = world.resource::<AppTypeRegistry>().clone();
let registry = registry.read();
let mut ast = world.resource_mut::<jackdaw_jsn::SceneJsnAst>();
ast.set_component_field(
self.entity,
&self.type_path,
&self.field_path,
self.old_value.clone(),
®istry,
);
if self.was_derived
&& let Some(node) = ast.node_for_entity_mut(self.entity)
{
node.derived_components.insert(self.type_path.clone());
}
}
apply_jsn_field_to_ecs(
world,
self.entity,
&self.type_path,
&self.field_path,
&self.old_value,
);
}
fn description(&self) -> &str {
"Set component field"
}
}
fn apply_jsn_field_to_ecs(
world: &mut World,
entity: Entity,
type_path: &str,
field_path: &str,
value: &serde_json::Value,
) {
let registry = world.resource::<AppTypeRegistry>().clone();
let registry = registry.read();
let Some(registration) = registry.get_with_type_path(type_path) else {
return;
};
let Some(reflect_component) = registration.data::<ReflectComponent>() else {
return;
};
if field_path.is_empty() {
let deserializer =
bevy::reflect::serde::TypedReflectDeserializer::new(registration, ®istry);
if let Ok(reflected) = deserializer.deserialize(value) {
reflect_component.insert(&mut world.entity_mut(entity), reflected.as_ref(), ®istry);
}
} else {
let Some(reflected) = reflect_component.reflect_mut(world.entity_mut(entity)) else {
return;
};
if let Ok(field) = reflected.into_inner().reflect_path_mut(field_path) {
apply_json_to_reflect(field, value, ®istry);
}
}
}
fn apply_json_to_reflect(
field: &mut dyn bevy::reflect::PartialReflect,
value: &serde_json::Value,
registry: &bevy::reflect::TypeRegistry,
) {
match value {
serde_json::Value::Number(n) => {
if let Some(f) = field.try_downcast_mut::<f32>() {
*f = n.as_f64().unwrap_or_default() as f32;
} else if let Some(f) = field.try_downcast_mut::<f64>() {
*f = n.as_f64().unwrap_or_default();
} else if let Some(i) = field.try_downcast_mut::<i32>() {
*i = n.as_i64().unwrap_or_default() as i32;
} else if let Some(i) = field.try_downcast_mut::<u32>() {
*i = n.as_u64().unwrap_or_default() as u32;
} else if let Some(i) = field.try_downcast_mut::<usize>() {
*i = n.as_u64().unwrap_or_default() as usize;
} else if let Some(i) = field.try_downcast_mut::<i8>() {
*i = n.as_i64().unwrap_or_default() as i8;
} else if let Some(i) = field.try_downcast_mut::<i16>() {
*i = n.as_i64().unwrap_or_default() as i16;
} else if let Some(i) = field.try_downcast_mut::<i64>() {
*i = n.as_i64().unwrap_or_default();
} else if let Some(i) = field.try_downcast_mut::<u8>() {
*i = n.as_u64().unwrap_or_default() as u8;
} else if let Some(i) = field.try_downcast_mut::<u16>() {
*i = n.as_u64().unwrap_or_default() as u16;
} else if let Some(i) = field.try_downcast_mut::<u64>() {
*i = n.as_u64().unwrap_or_default();
}
}
serde_json::Value::Bool(b) => {
if let Some(f) = field.try_downcast_mut::<bool>() {
*f = *b;
}
}
serde_json::Value::String(s) => {
if let Some(f) = field.try_downcast_mut::<String>() {
*f = s.clone();
return;
}
try_typed_deserialize(field, value, registry);
}
serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
try_typed_deserialize(field, value, registry);
}
serde_json::Value::Null => {}
}
}
fn try_typed_deserialize(
field: &mut dyn bevy::reflect::PartialReflect,
value: &serde_json::Value,
registry: &bevy::reflect::TypeRegistry,
) {
let Some(type_info) = field.get_represented_type_info() else {
return;
};
let Some(registration) = registry.get(type_info.type_id()) else {
return;
};
let deserializer = bevy::reflect::serde::TypedReflectDeserializer::new(registration, registry);
if let Ok(reflected) = deserializer.deserialize(value) {
field.apply(reflected.as_ref());
}
}
pub fn sync_component_to_ast<T: bevy::reflect::Reflect>(
world: &mut World,
entity: Entity,
type_path: &str,
value: &T,
) {
let registry = world.resource::<AppTypeRegistry>().clone();
let registry = registry.read();
let processor = crate::scene_io::AstSerializerProcessor;
let serializer =
bevy::reflect::serde::TypedReflectSerializer::with_processor(value, ®istry, &processor);
if let Ok(json_value) = serde_json::to_value(&serializer) {
drop(registry);
world
.resource_mut::<jackdaw_jsn::SceneJsnAst>()
.set_component(entity, type_path, json_value);
}
}
pub fn sync_required_to_ast(world: &mut World, entity: Entity) -> Vec<String> {
use std::collections::HashSet;
let registry = world.resource::<AppTypeRegistry>().clone();
let reg = registry.read();
let existing: HashSet<String> = world
.resource::<jackdaw_jsn::SceneJsnAst>()
.node_for_entity(entity)
.map(|n| n.components.keys().cloned().collect())
.unwrap_or_default();
let skip_ids: HashSet<TypeId> = HashSet::from([
TypeId::of::<GlobalTransform>(),
TypeId::of::<InheritedVisibility>(),
TypeId::of::<ViewVisibility>(),
TypeId::of::<ChildOf>(),
TypeId::of::<Children>(),
]);
let processor = crate::scene_io::AstSerializerProcessor;
let Ok(entity_ref) = world.get_entity(entity) else {
return vec![];
};
let mut to_add: Vec<(String, serde_json::Value)> = Vec::new();
for registration in reg.iter() {
if skip_ids.contains(®istration.type_id()) {
continue;
}
let type_path = registration
.type_info()
.type_path_table()
.path()
.to_string();
if existing.contains(&type_path) {
continue;
}
if crate::scene_io::should_skip_component(&type_path) {
continue;
}
let Some(reflect_component) = registration.data::<ReflectComponent>() else {
continue;
};
let Some(component) = reflect_component.reflect(entity_ref) else {
continue;
};
let serializer = bevy::reflect::serde::TypedReflectSerializer::with_processor(
component, ®, &processor,
);
if let Ok(value) = serde_json::to_value(&serializer) {
to_add.push((type_path, value));
}
}
drop(reg);
let promoted: Vec<String> = to_add.iter().map(|(path, _)| path.clone()).collect();
if !promoted.is_empty() {
info!(
"sync_required_to_ast: {} derived components promoted for entity {entity}",
promoted.len()
);
let mut ast = world.resource_mut::<jackdaw_jsn::SceneJsnAst>();
for (type_path, value) in to_add {
ast.set_component(entity, &type_path, value);
}
if let Some(node) = ast.node_for_entity_mut(entity) {
for path in &promoted {
node.derived_components.insert(path.clone());
}
}
}
promoted
}