mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
Root cause: notify-debouncer-full with macos_kqueue caused 25K+ directory watch registrations to take >170s on large projects. Changes: - Replace notify-debouncer-full with notify-debouncer-mini to avoid FSEvents Create+Remove event cancellation and heavy per-watch overhead - Remove macos_kqueue feature; use FSEvents (matches upstream Rojo) - Fix VFS read guard: watch_enabled && watch_recursive -> watch_enabled so recording mechanism works when watch_recursive is false - Build RefPathIndex from patch_apply data instead of redundant 4.8s WalkDir + 5,854 file re-reads (populate_from_dir) - Use ServeSession::new_oneshot() for sourcemap without --watch - Restore simple synchronous watch setup (implicit during reads) Serve: >180s (never completed) -> ~8s to Listening Sourcemap: 14.3s -> ~9s All 489 tests passing.
144 lines
4.9 KiB
Rust
144 lines
4.9 KiB
Rust
//! 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<Ref>,
|
|
pub added_instances: Vec<PatchAdd>,
|
|
pub updated_instances: Vec<PatchUpdate>,
|
|
/// 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<Ref>,
|
|
/// 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<std::path::PathBuf>,
|
|
}
|
|
|
|
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<String>,
|
|
pub changed_class_name: Option<Ustr>,
|
|
|
|
/// Contains all changed properties. If a property is assigned to `None`,
|
|
/// then that property has been removed.
|
|
pub changed_properties: UstrMap<Option<Variant>>,
|
|
|
|
/// Changed Rojo-specific metadata, if any of it changed.
|
|
pub changed_metadata: Option<InstanceMetadata>,
|
|
}
|
|
|
|
/// 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<Ref>,
|
|
pub added: Vec<Ref>,
|
|
pub updated: Vec<AppliedPatchUpdate>,
|
|
|
|
/// 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>) -> 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<String>,
|
|
pub changed_class_name: Option<Ustr>,
|
|
pub changed_properties: UstrMap<Option<Variant>>,
|
|
pub changed_metadata: Option<InstanceMetadata>,
|
|
}
|
|
|
|
impl AppliedPatchUpdate {
|
|
pub fn new(id: Ref) -> Self {
|
|
Self {
|
|
id,
|
|
changed_name: None,
|
|
changed_class_name: None,
|
|
changed_properties: UstrMap::new(),
|
|
changed_metadata: None,
|
|
}
|
|
}
|
|
}
|