This commit is contained in:
SriBalan 2026-10-06 01:20:58 -04:00 • committed by GitHub
commit dfc9c75934
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 35 additions and 10 deletions

View file

@ -235,7 +235,6 @@ function SettingsPage:render()
description = "Editing files in Studio will sync them into the filesystem", description = "Editing files in Studio will sync them into the filesystem",
locked = self.props.syncActive, locked = self.props.syncActive,
lockedTooltip = "(Cannot change while currently syncing. Disconnect first.)", lockedTooltip = "(Cannot change while currently syncing. Disconnect first.)",
tag = "unstable",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = layoutIncrement(), layoutOrder = layoutIncrement(),
}), }),

View file

@ -60,8 +60,6 @@ function ChangeBatcher:__cycle(dt)
self.__onChangesFlushed(patch) self.__onChangesFlushed(patch)
end end
end end
self.__instanceMap:unpauseAllInstances()
end end
function ChangeBatcher:__flush() function ChangeBatcher:__flush()

View file

@ -145,7 +145,7 @@ end
--[[ --[[
Pause updates for an instance. Pause updates for an instance.
]] ]]
function InstanceMap:pauseInstance(instance) function InstanceMap:pauseInstance(instance, duration)
local id = self.fromInstances[instance] local id = self.fromInstances[instance]
-- If we don't know about this instance, ignore it. -- If we don't know about this instance, ignore it.
@ -153,7 +153,8 @@ function InstanceMap:pauseInstance(instance)
return return
end end
self.pausedUpdateInstances[instance] = true -- Pause with expiration timestamp (0.6s) to ensure property replication signals settle
self.pausedUpdateInstances[instance] = os.clock() + (duration or 0.6)
end end
--[[ --[[
@ -202,8 +203,13 @@ end
function InstanceMap:__maybeFireInstanceChanged(instance, propertyName) function InstanceMap:__maybeFireInstanceChanged(instance, propertyName)
Log.trace("{}.{} changed", instance:GetFullName(), propertyName) Log.trace("{}.{} changed", instance:GetFullName(), propertyName)
if self.pausedUpdateInstances[instance] then local pauseExpiry = self.pausedUpdateInstances[instance]
if pauseExpiry ~= nil then
if os.clock() < pauseExpiry then
return return
else
self.pausedUpdateInstances[instance] = nil
end
end end
if self.onInstanceChanged == nil then if self.onInstanceChanged == nil then

View file

@ -2,7 +2,9 @@ use crossbeam_channel::{select, Receiver, RecvError, Sender};
use jod_thread::JoinHandle; use jod_thread::JoinHandle;
use memofs::{IoResultExt, Vfs, VfsEvent}; use memofs::{IoResultExt, Vfs, VfsEvent};
use rbx_dom_weak::types::{Ref, Variant}; use rbx_dom_weak::types::{Ref, Variant};
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, Instant};
use std::{ use std::{
fs, fs,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
@ -54,10 +56,12 @@ impl ChangeProcessor {
) -> Self { ) -> Self {
let (shutdown_sender, shutdown_receiver) = crossbeam_channel::bounded(1); let (shutdown_sender, shutdown_receiver) = crossbeam_channel::bounded(1);
let vfs_receiver = vfs.event_receiver(); let vfs_receiver = vfs.event_receiver();
let recent_writes = Arc::new(Mutex::new(HashMap::new()));
let task = JobThreadContext { let task = JobThreadContext {
tree, tree,
vfs, vfs,
message_queue, message_queue,
recent_writes,
}; };
let job_thread = jod_thread::Builder::new() let job_thread = jod_thread::Builder::new()
@ -111,6 +115,10 @@ struct JobThreadContext {
/// Whenever changes are applied to the DOM, we should push those changes /// Whenever changes are applied to the DOM, we should push those changes
/// into this message queue to inform any connected clients. /// into this message queue to inform any connected clients.
message_queue: Arc<MessageQueue<AppliedPatchSet>>, message_queue: Arc<MessageQueue<AppliedPatchSet>>,
/// Tracks recent filesystem writes initiated by Studio client two-way sync,
/// suppressing file-watcher echo loops back into the message queue.
recent_writes: Arc<Mutex<HashMap<PathBuf, Instant>>>,
} }
impl JobThreadContext { impl JobThreadContext {
@ -165,6 +173,18 @@ impl JobThreadContext {
.commit_event(&event) .commit_event(&event)
.expect("Error applying VFS change"); .expect("Error applying VFS change");
// Suppress echo if this event corresponds to a write triggered by two-way sync
if let VfsEvent::Create(path) | VfsEvent::Write(path) = &event {
if let Ok(canonical_path) = self.vfs.canonicalize(path) {
let mut writes = self.recent_writes.lock().unwrap();
writes.retain(|_, time| time.elapsed() < Duration::from_secs(3));
if writes.remove(&canonical_path).is_some() {
log::trace!("Suppressing VFS echo for client-originated write: {:?}", canonical_path);
return;
}
}
}
// For a given VFS event, we might have many changes to different parts // For a given VFS event, we might have many changes to different parts
// of the tree. Calculate and apply all of these changes. // of the tree. Calculate and apply all of these changes.
let applied_patches = match event { let applied_patches = match event {
@ -258,6 +278,9 @@ impl JobThreadContext {
path.display(), path.display(),
err err
); );
} else if let Ok(canonical_path) = self.vfs.canonicalize(path) {
let mut writes = self.recent_writes.lock().unwrap();
writes.insert(canonical_path, Instant::now());
} }
} else { } else {
log::warn!("Cannot change Source to non-string value."); log::warn!("Cannot change Source to non-string value.");
@ -288,9 +311,8 @@ impl JobThreadContext {
apply_patch_set(&mut tree, patch_set) apply_patch_set(&mut tree, patch_set)
}; };
if !applied_patch.is_empty() { // Suppress pushing self-generated patch back to message queue to eliminate client echo
self.message_queue.push_messages(&[applied_patch]); let _ = applied_patch;
}
} }
} }