//! Defines the data structures used for describing instance patches. use std::{collections::HashSet, path::PathBuf}; use rbx_dom_weak::{ types::{Ref, Variant}, HashMapExt as _, Ustr, UstrMap, }; use serde::{Deserialize, Serialize}; use super::{InstanceMetadata, InstanceSnapshot}; /// A set of different kinds of patches that can be applied to an WeakDom. /// /// These patches shouldn't be persisted: there's no mechanism in place to make /// sure that another patch wasn't applied before this one that could cause a /// conflict! #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct PatchSet { pub removed_instances: Vec, pub added_instances: Vec, pub updated_instances: Vec, /// Instance IDs whose backing files should be staged via git add after writing. /// Populated from WriteRequest.stage_ids, consumed by change_processor. #[serde(default, skip)] pub stage_ids: HashSet, /// Pre-resolved filesystem paths to stage via git add. /// Populated by api.rs (for files already on disk), consumed by change_processor /// together with any Source write paths from stage_ids. /// All staging is consolidated into a single git_add call to avoid index.lock races. #[serde(default, skip)] pub stage_paths: Vec, } impl PatchSet { pub fn new() -> Self { PatchSet { removed_instances: Vec::new(), added_instances: Vec::new(), updated_instances: Vec::new(), stage_ids: HashSet::new(), stage_paths: Vec::new(), } } } /// A patch containing an instance that was added to the tree. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PatchAdd { pub parent_id: Ref, pub instance: InstanceSnapshot, } /// A patch indicating that properties of an instance changed. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PatchUpdate { pub id: Ref, pub changed_name: Option, pub changed_class_name: Option, /// Contains all changed properties. If a property is assigned to `None`, /// then that property has been removed. pub changed_properties: UstrMap>, /// Changed Rojo-specific metadata, if any of it changed. pub changed_metadata: Option, } /// Applied patch sets have the same rough shape as PatchSet, but are /// descriptive of the operation that happened instead of prescribing what /// mutations to apply to the tree. /// /// Applied patch sets are generated by applying a patch to a tree, and are /// suitable for sending over the network to a synchronized tree like the Rojo /// Studio plugin. /// // TODO: Introduce machinery to detect conflicts, like keeping previous + // current values in all fields. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct AppliedPatchSet { pub removed: Vec, pub added: Vec, pub updated: Vec, /// Collected during patch application: `(resolved_absolute_ref_path, meta_or_model_file_path)`. /// Used to build `RefPathIndex` without re-walking the directory tree. #[serde(skip)] pub ref_path_index_entries: Vec<(String, PathBuf)>, } impl AppliedPatchSet { pub fn new() -> Self { AppliedPatchSet { removed: Vec::new(), added: Vec::new(), updated: Vec::new(), ref_path_index_entries: Vec::new(), } } pub fn is_empty(&self) -> bool { self.removed.is_empty() && self.added.is_empty() && self.updated.is_empty() } /// Merge multiple patch sets into a single consolidated set. /// /// Different platforms deliver VFS events in different orders for the /// same filesystem operation (e.g., macOS FSEvents sends CREATE first, /// Windows ReadDirectoryChangesW sends REMOVE first for renames). /// Merging ensures the WebSocket message structure is identical /// regardless of event ordering. pub fn merge(patches: Vec) -> AppliedPatchSet { let mut merged = AppliedPatchSet::new(); for patch in patches { merged.removed.extend(patch.removed); merged.added.extend(patch.added); merged.updated.extend(patch.updated); } merged } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AppliedPatchUpdate { pub id: Ref, // TODO: Store previous values in order to detect application conflicts pub changed_name: Option, pub changed_class_name: Option, pub changed_properties: UstrMap>, pub changed_metadata: Option, } impl AppliedPatchUpdate { pub fn new(id: Ref) -> Self { Self { id, changed_name: None, changed_class_name: None, changed_properties: UstrMap::new(), changed_metadata: None, } } }