mod reversible;
pub mod snapshot;
pub use reversible::Reversible;
pub use snapshot::{
EntitySnapshot, apply_snapshot_to_entity, capture_hierarchy, recreate_hierarchy_with_mapping,
};
use crate::prelude::*;
pub struct HistoryEntry<T> {
pub operation: T,
pub description: String,
}
pub struct History<T> {
undo_stack: Vec<HistoryEntry<T>>,
redo_stack: Vec<HistoryEntry<T>>,
max_entries: usize,
}
impl<T: Reversible> History<T> {
pub fn new(max_entries: usize) -> Self {
Self {
undo_stack: Vec::new(),
redo_stack: Vec::new(),
max_entries,
}
}
pub fn push(&mut self, operation: T, description: impl Into<String>) {
self.redo_stack.clear();
self.undo_stack.push(HistoryEntry {
operation,
description: description.into(),
});
while self.undo_stack.len() > self.max_entries {
self.undo_stack.remove(0);
}
}
pub fn undo(&mut self, context: &mut T::Context) -> Option<T::Result> {
let entry = self.undo_stack.pop()?;
let (reversed_operation, result) = entry.operation.reverse(context);
self.redo_stack.push(HistoryEntry {
operation: reversed_operation,
description: entry.description,
});
Some(result)
}
pub fn redo(&mut self, context: &mut T::Context) -> Option<T::Result> {
let entry = self.redo_stack.pop()?;
let (reversed_operation, result) = entry.operation.reverse(context);
self.undo_stack.push(HistoryEntry {
operation: reversed_operation,
description: entry.description,
});
Some(result)
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn undo_count(&self) -> usize {
self.undo_stack.len()
}
pub fn redo_count(&self) -> usize {
self.redo_stack.len()
}
pub fn undo_stack(&self) -> &[HistoryEntry<T>] {
&self.undo_stack
}
pub fn redo_stack(&self) -> &[HistoryEntry<T>] {
&self.redo_stack
}
pub fn for_each_entry_mut(&mut self, mut callback: impl FnMut(&mut T)) {
for entry in &mut self.undo_stack {
callback(&mut entry.operation);
}
for entry in &mut self.redo_stack {
callback(&mut entry.operation);
}
}
pub fn clear(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
}
}
impl<T: Reversible> Default for History<T> {
fn default() -> Self {
Self::new(100)
}
}
pub type UndoHistory = History<UndoableOperation>;
#[derive(Clone)]
pub struct HierarchyNode {
pub entity: Entity,
pub snapshot: EntitySnapshot,
pub children: Vec<HierarchyNode>,
}
impl HierarchyNode {
fn remap(&self, mapping: &std::collections::HashMap<Entity, Entity>) -> Self {
let mut snapshot = self.snapshot.clone();
if let Some(parent) = snapshot.parent_entity
&& let Some(&new_parent) = mapping.get(&parent)
{
snapshot.parent_entity = Some(new_parent);
}
Self {
entity: mapping.get(&self.entity).copied().unwrap_or(self.entity),
snapshot,
children: self.children.iter().map(|c| c.remap(mapping)).collect(),
}
}
}
#[derive(Clone)]
pub enum UndoableOperation {
EntityCreated {
hierarchy: Box<HierarchyNode>,
current_entity: Entity,
},
EntityDeleted {
hierarchy: Box<HierarchyNode>,
deleted_entity: Entity,
},
TransformChanged {
entity: Entity,
old_transform: LocalTransform,
new_transform: LocalTransform,
},
BulkTransformChanged {
transforms: Vec<(Entity, LocalTransform, LocalTransform)>,
},
BulkEntitiesCreated {
hierarchies: Vec<HierarchyNode>,
entities: Vec<Entity>,
},
BulkEntitiesDeleted {
hierarchies: Vec<HierarchyNode>,
deleted_entities: Vec<Entity>,
},
ComponentChanged {
entity: Entity,
snapshot_before: Box<EntitySnapshot>,
snapshot_after: Box<EntitySnapshot>,
},
}
impl UndoableOperation {
pub fn remap_entities(&mut self, mapping: &std::collections::HashMap<Entity, Entity>) {
match self {
UndoableOperation::EntityCreated {
hierarchy,
current_entity,
} => {
if let Some(&new_id) = mapping.get(current_entity) {
*current_entity = new_id;
}
*hierarchy = Box::new(hierarchy.remap(mapping));
}
UndoableOperation::EntityDeleted {
hierarchy,
deleted_entity,
} => {
if let Some(&new_id) = mapping.get(deleted_entity) {
*deleted_entity = new_id;
}
*hierarchy = Box::new(hierarchy.remap(mapping));
}
UndoableOperation::TransformChanged { entity, .. } => {
if let Some(&new_id) = mapping.get(entity) {
*entity = new_id;
}
}
UndoableOperation::BulkTransformChanged { transforms } => {
for (entity, _, _) in transforms.iter_mut() {
if let Some(&new_id) = mapping.get(entity) {
*entity = new_id;
}
}
}
UndoableOperation::BulkEntitiesCreated {
hierarchies,
entities,
} => {
for entity in entities.iter_mut() {
if let Some(&new_id) = mapping.get(entity) {
*entity = new_id;
}
}
*hierarchies = hierarchies.iter().map(|h| h.remap(mapping)).collect();
}
UndoableOperation::BulkEntitiesDeleted {
hierarchies,
deleted_entities,
} => {
for entity in deleted_entities.iter_mut() {
if let Some(&new_id) = mapping.get(entity) {
*entity = new_id;
}
}
*hierarchies = hierarchies.iter().map(|h| h.remap(mapping)).collect();
}
UndoableOperation::ComponentChanged { entity, .. } => {
if let Some(&new_id) = mapping.get(entity) {
*entity = new_id;
}
}
}
}
}
impl UndoableOperation {
pub fn validate_entity_references(&self, world: &World) -> bool {
match self {
UndoableOperation::EntityCreated { current_entity, .. } => {
entity_exists(world, *current_entity)
}
UndoableOperation::EntityDeleted { .. } => true,
UndoableOperation::TransformChanged { entity, .. } => entity_exists(world, *entity),
UndoableOperation::BulkTransformChanged { transforms } => transforms
.iter()
.all(|(entity, _, _)| entity_exists(world, *entity)),
UndoableOperation::BulkEntitiesCreated { entities, .. } => {
entities.iter().all(|entity| entity_exists(world, *entity))
}
UndoableOperation::BulkEntitiesDeleted { .. } => true,
UndoableOperation::ComponentChanged { entity, .. } => entity_exists(world, *entity),
}
}
}
pub struct UndoResult {
pub select_entity: Option<Entity>,
pub entity_mapping: Option<std::collections::HashMap<Entity, Entity>>,
}
pub fn begin_transform_change(
pending: &mut Option<(Entity, LocalTransform)>,
entity: Entity,
transform: LocalTransform,
) {
if pending.is_none() {
*pending = Some((entity, transform));
}
}
pub fn commit_transform_change(
pending: &mut Option<(Entity, LocalTransform)>,
history: &mut UndoHistory,
entity: Entity,
new_transform: LocalTransform,
) {
if let Some((pending_entity, old_transform)) = pending.take()
&& pending_entity == entity
&& old_transform != new_transform
{
history.push(
UndoableOperation::TransformChanged {
entity,
old_transform,
new_transform,
},
"Transform changed",
);
}
}
pub fn push_bulk_transform_change(
history: &mut UndoHistory,
transforms: Vec<(Entity, LocalTransform, LocalTransform)>,
description: impl Into<String>,
) {
let transforms: Vec<_> = transforms
.into_iter()
.filter(|(_, old, new)| old != new)
.collect();
if !transforms.is_empty() {
history.push(
UndoableOperation::BulkTransformChanged { transforms },
description,
);
}
}
pub fn push_bulk_entities_created(
history: &mut UndoHistory,
world: &World,
entities: Vec<Entity>,
description: impl Into<String>,
) {
let hierarchies: Vec<HierarchyNode> = entities
.iter()
.map(|&entity| capture_hierarchy(world, entity))
.collect();
history.push(
UndoableOperation::BulkEntitiesCreated {
hierarchies,
entities,
},
description,
);
}
pub fn push_bulk_entities_deleted(
history: &mut UndoHistory,
world: &World,
entities: Vec<Entity>,
description: impl Into<String>,
) {
let hierarchies: Vec<HierarchyNode> = entities
.iter()
.map(|&entity| capture_hierarchy(world, entity))
.collect();
history.push(
UndoableOperation::BulkEntitiesDeleted {
hierarchies,
deleted_entities: entities,
},
description,
);
}
pub fn push_component_change(
history: &mut UndoHistory,
entity: Entity,
snapshot_before: EntitySnapshot,
snapshot_after: EntitySnapshot,
description: impl Into<String>,
) {
history.push(
UndoableOperation::ComponentChanged {
entity,
snapshot_before: Box::new(snapshot_before),
snapshot_after: Box::new(snapshot_after),
},
description,
);
}
fn apply_history_action(
history: &mut UndoHistory,
world: &mut World,
pending_transform: &mut Option<(Entity, LocalTransform)>,
is_undo: bool,
) -> Option<UndoResult> {
let result = if is_undo {
history.undo(world)?
} else {
history.redo(world)?
};
if let Some(mapping) = &result.entity_mapping {
history.for_each_entry_mut(|op| op.remap_entities(mapping));
if let Some(pending) = pending_transform
&& let Some(&new_id) = mapping.get(&pending.0)
{
pending.0 = new_id;
}
}
Some(result)
}
pub fn perform_undo(
history: &mut UndoHistory,
world: &mut World,
pending_transform: &mut Option<(Entity, LocalTransform)>,
) -> Option<UndoResult> {
apply_history_action(history, world, pending_transform, true)
}
pub fn perform_redo(
history: &mut UndoHistory,
world: &mut World,
pending_transform: &mut Option<(Entity, LocalTransform)>,
) -> Option<UndoResult> {
apply_history_action(history, world, pending_transform, false)
}
pub fn entity_exists(world: &World, entity: Entity) -> bool {
world.get_local_transform(entity).is_some()
}