nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
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()
}