/// <summary> /// Executes <paramref name="updateFunc"/> on the current snapshot within a lock. /// If a different snapshot object is returned, <see cref="CurrentSnapshot"/> is updated /// and an invocation of <see cref="SnapshotChanged"/> is scheduled. /// </summary> private void TryUpdateSnapshot(Func <DependenciesSnapshot, DependenciesSnapshot> updateFunc, CancellationToken token = default) { lock (_snapshotLock) { DependenciesSnapshot updatedSnapshot = updateFunc(_currentSnapshot); if (ReferenceEquals(_currentSnapshot, updatedSnapshot)) { return; } _currentSnapshot = updatedSnapshot; } // avoid unnecessary tree updates _dependenciesUpdateScheduler.ScheduleAsyncTask( ct => { if (ct.IsCancellationRequested || IsDisposing || IsDisposed) { return(Task.FromCanceled(ct)); } IDependenciesSnapshot snapshot = _currentSnapshot; if (snapshot != null) { SnapshotChanged?.Invoke(this, new SnapshotChangedEventArgs(snapshot, ct)); } return(Task.CompletedTask); }, token); }
private void TryUpdateSnapshot(Func <DependenciesSnapshot, DependenciesSnapshot> updateFunc, CancellationToken token = default) { lock (_snapshotLock) { DependenciesSnapshot updatedSnapshot = updateFunc(_currentSnapshot); if (ReferenceEquals(_currentSnapshot, updatedSnapshot)) { return; } _currentSnapshot = updatedSnapshot; } // Conflate rapid snapshot updates by debouncing events over a short window. // This reduces the frequency of tree updates with minimal perceived latency. _dependenciesUpdateScheduler.ScheduleAsyncTask( ct => { if (ct.IsCancellationRequested || IsDisposing || IsDisposed) { return(Task.FromCanceled(ct)); } // Always publish the latest snapshot IDependenciesSnapshot snapshot = _currentSnapshot; var events = new SnapshotChangedEventArgs(snapshot, ct); _snapshotChangedSource.Post(events); SnapshotChanged?.Invoke(this, events); return(Task.CompletedTask); }, token); }
public void RegisterSnapshotProvider(IDependenciesSnapshotProvider snapshotProvider) { if (snapshotProvider == null) { return; } lock (_snapshotProviders) { _snapshotProviders[snapshotProvider.CurrentSnapshot.ProjectPath] = snapshotProvider; snapshotProvider.SnapshotRenamed += OnSnapshotRenamed; snapshotProvider.SnapshotChanged += OnSnapshotChanged; snapshotProvider.SnapshotProviderUnloading += OnSnapshotProviderUnloading; } void OnSnapshotProviderUnloading(object sender, SnapshotProviderUnloadingEventArgs e) { // Project has unloaded, so remove it from the cache and unregister event handlers SnapshotProviderUnloading?.Invoke(this, e); lock (_snapshotProviders) { _snapshotProviders.Remove(snapshotProvider.CurrentSnapshot.ProjectPath); snapshotProvider.SnapshotRenamed -= OnSnapshotRenamed; snapshotProvider.SnapshotChanged -= OnSnapshotChanged; snapshotProvider.SnapshotProviderUnloading -= OnSnapshotProviderUnloading; } } void OnSnapshotRenamed(object sender, ProjectRenamedEventArgs e) { lock (_snapshotProviders) { // Remove and re-add provider with new project path if (!string.IsNullOrEmpty(e.OldFullPath) && _snapshotProviders.TryGetValue(e.OldFullPath, out IDependenciesSnapshotProvider provider) && _snapshotProviders.Remove(e.OldFullPath) && provider != null && !string.IsNullOrEmpty(e.NewFullPath)) { _snapshotProviders[e.NewFullPath] = provider; } } } void OnSnapshotChanged(object sender, SnapshotChangedEventArgs e) { // Propagate the change event SnapshotChanged?.Invoke(this, e); } }
private void ScheduleDependenciesUpdate(CancellationToken token = default) { _dependenciesUpdateScheduler.ScheduleAsyncTask(ct => { if (ct.IsCancellationRequested || IsDisposing || IsDisposed) { return(Task.FromCanceled(ct)); } IDependenciesSnapshot snapshot = _currentSnapshot; if (snapshot != null) { SnapshotChanged?.Invoke(this, new SnapshotChangedEventArgs(snapshot, ct)); } return(Task.CompletedTask); }, token); }
private void OnSnapshotChanged(object sender, SnapshotChangedEventArgs e) { SnapshotChanged?.Invoke(this, e); }
private void OnDependenciesSnapshotChanged(IDependenciesSnapshot snapshot) { SnapshotChanged?.Invoke(this, new SnapshotChangedEventArgs(snapshot)); }
/// <inheritdoc /> public IDisposable RegisterSnapshotProvider(IDependenciesSnapshotProvider snapshotProvider) { Requires.NotNull(snapshotProvider, nameof(snapshotProvider)); var unregister = new DisposableBag(); lock (_lock) { snapshotProvider.SnapshotRenamed += OnSnapshotRenamed; snapshotProvider.SnapshotProviderUnloading += OnSnapshotProviderUnloading; ITargetBlock <SnapshotChangedEventArgs> actionBlock = DataflowBlockSlim.CreateActionBlock <SnapshotChangedEventArgs>( e => SnapshotChanged?.Invoke(this, e), "AggregateDependenciesSnapshotProviderSource {1}", skipIntermediateInputData: true); unregister.Add( snapshotProvider.SnapshotChangedSource.LinkTo( actionBlock, DataflowOption.PropagateCompletion)); _snapshotProviderByPath = _snapshotProviderByPath.SetItem(snapshotProvider.CurrentSnapshot.ProjectPath, snapshotProvider); } unregister.Add(new DisposableDelegate( () => { lock (_lock) { string projectPath = snapshotProvider.CurrentSnapshot.ProjectPath; _snapshotProviderByPath = _snapshotProviderByPath.Remove(projectPath); snapshotProvider.SnapshotRenamed -= OnSnapshotRenamed; snapshotProvider.SnapshotProviderUnloading -= OnSnapshotProviderUnloading; } })); return(unregister); void OnSnapshotProviderUnloading(object sender, SnapshotProviderUnloadingEventArgs e) { // Project has unloaded, so remove it from the cache and unregister event handlers SnapshotProviderUnloading?.Invoke(this, e); unregister.Dispose(); } void OnSnapshotRenamed(object sender, ProjectRenamedEventArgs e) { if (string.IsNullOrEmpty(e.OldFullPath)) { return; } lock (_lock) { // Remove and re-add provider with new project path if (_snapshotProviderByPath.TryGetValue(e.OldFullPath, out IDependenciesSnapshotProvider provider)) { _snapshotProviderByPath = _snapshotProviderByPath.Remove(e.OldFullPath); if (!string.IsNullOrEmpty(e.NewFullPath)) { _snapshotProviderByPath = _snapshotProviderByPath.SetItem(e.NewFullPath, provider); } } } } }