/// <summary> /// Creates a source block that produces a transformed value for each value from original source block, /// skipping intermediate input and output states, and hence is not suitable for producing or consuming /// deltas. /// </summary> /// <typeparam name="TOut"> /// The type of value produced by <paramref name="transform"/>. /// </typeparam> /// <param name="source"> /// The source block whose values are to be transformed. /// </param> /// <param name="transform"> /// The function to execute on each value from <paramref name="source"/>. /// </param> /// <param name="suppressVersionOnlyUpdates"> /// A value indicating whether to prevent messages from propagating to the target /// block if no project changes are include other than an incremented version number. /// </param> /// <param name="ruleNames"> /// The names of the rules that describe the project data the caller is interested in. /// </param> /// <returns> /// The transformed source block and a disposable value that terminates the link. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="source"/> is <see langword="null"/>. /// <para> /// -or- /// </para> /// <paramref name="transform"/> is <see langword="null"/>. /// </exception> public static DisposableValue <ISourceBlock <TOut> > TransformWithNoDelta <TOut>( this ISourceBlock <IProjectVersionedValue <IProjectSubscriptionUpdate> > source, Func <IProjectVersionedValue <IProjectSubscriptionUpdate>, TOut> transform, bool suppressVersionOnlyUpdates, IEnumerable <string>?ruleNames = null) { Requires.NotNull(source, nameof(source)); Requires.NotNull(transform, nameof(transform)); IPropagatorBlock <IProjectVersionedValue <IProjectSubscriptionUpdate>, TOut> transformBlock = DataflowBlockSlim.CreateTransformBlock(transform, skipIntermediateInputData: true, skipIntermediateOutputData: true); IDisposable link = source.LinkTo(transformBlock, DataflowOption.PropagateCompletion, initialDataAsNew: true, suppressVersionOnlyUpdates: suppressVersionOnlyUpdates, ruleNames: ruleNames); return(new DisposableValue <ISourceBlock <TOut> >(transformBlock, link)); }
protected override IDisposable LinkExternalInput(ITargetBlock <IProjectVersionedValue <UpToDateCheckImplicitConfiguredInput> > targetBlock) { Assumes.Present(_configuredProject.Services.ProjectSubscription); bool attemptedStateRestore = false; // Initial state is empty. We will evolve this reference over time, updating it iteratively // on each new data update. UpToDateCheckImplicitConfiguredInput state = UpToDateCheckImplicitConfiguredInput.CreateEmpty(_configuredProject.ProjectConfiguration); IPropagatorBlock <IProjectVersionedValue <UpdateValues>, IProjectVersionedValue <UpToDateCheckImplicitConfiguredInput> > transformBlock = DataflowBlockSlim.CreateTransformBlock <IProjectVersionedValue <UpdateValues>, IProjectVersionedValue <UpToDateCheckImplicitConfiguredInput> >(TransformAsync); IProjectValueDataSource <IProjectSubscriptionUpdate> source1 = _configuredProject.Services.ProjectSubscription.JointRuleSource; IProjectValueDataSource <IProjectSubscriptionUpdate> source2 = _configuredProject.Services.ProjectSubscription.SourceItemsRuleSource; IProjectItemSchemaService source3 = _projectItemSchemaService; IProjectValueDataSource <IProjectCatalogSnapshot> source4 = _configuredProject.Services.ProjectSubscription.ProjectCatalogSource; return(new DisposableBag { // Sync-link various sources to our transform block ProjectDataSources.SyncLinkTo( source1.SourceBlock.SyncLinkOptions(DataflowOption.WithRuleNames(ProjectPropertiesSchemas)), source2.SourceBlock.SyncLinkOptions(), source3.SourceBlock.SyncLinkOptions(), source4.SourceBlock.SyncLinkOptions(), target: transformBlock, linkOptions: DataflowOption.PropagateCompletion, CancellationToken.None), // Link the transform block to our target block transformBlock.LinkTo(targetBlock, DataflowOption.PropagateCompletion), JoinUpstreamDataSources(source1, source2, source3, source4) }); async Task <IProjectVersionedValue <UpToDateCheckImplicitConfiguredInput> > TransformAsync(IProjectVersionedValue <UpdateValues> e) { if (!attemptedStateRestore) { attemptedStateRestore = true; if (_persistentState is not null) { // Restoring state requires the UI thread. We must use JTF.RunAsync here to ensure the UI // thread is shared between related work and prevent deadlocks. (int ItemHash, DateTime InputsChangedAtUtc)? restoredState = await JoinableFactory.RunAsync(() => _persistentState.RestoreStateAsync(_configuredProject.UnconfiguredProject.FullPath, _configuredProject.ProjectConfiguration.Dimensions)); if (restoredState is not null) { state = state.WithRestoredState(restoredState.Value.ItemHash, restoredState.Value.InputsChangedAtUtc); } } } int? priorItemHash = state.ItemHash; DateTime priorLastItemsChangedAtUtc = state.LastItemsChangedAtUtc; state = state.Update( jointRuleUpdate: e.Value.Item1, sourceItemsUpdate: e.Value.Item2, projectItemSchema: e.Value.Item3, projectCatalogSnapshot: e.Value.Item4); if (state.ItemHash is not null && _persistentState is not null && (priorItemHash != state.ItemHash || priorLastItemsChangedAtUtc != state.LastItemsChangedAtUtc)) { _persistentState.StoreState(_configuredProject.UnconfiguredProject.FullPath, _configuredProject.ProjectConfiguration.Dimensions, state.ItemHash.Value, state.LastItemsChangedAtUtc); } return(new ProjectVersionedValue <UpToDateCheckImplicitConfiguredInput>(state, e.DataSourceVersions)); } }
/// <summary> /// Creates a source block that produces a transformed value for each value from original source block, /// skipping intermediate input and output states, and hence is not suitable for producing or consuming /// deltas. /// </summary> /// <typeparam name="TInput"> /// The type of the input value produced by <paramref name="source"/>. /// </typeparam> /// <typeparam name="TOut"> /// The type of value produced by <paramref name="transform"/>. /// </typeparam> /// <param name="source"> /// The source block whose values are to be transformed. /// </param> /// <param name="transform"> /// The function to execute on each value from <paramref name="source"/>. /// </param> /// <returns> /// The transformed source block and a disposable value that terminates the link. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="source"/> is <see langword="null"/>. /// <para> /// -or- /// </para> /// <paramref name="transform"/> is <see langword="null"/>. /// </exception> public static DisposableValue <ISourceBlock <TOut> > TransformWithNoDelta <TInput, TOut>( this ISourceBlock <IProjectVersionedValue <TInput> > source, Func <IProjectVersionedValue <TInput>, TOut> transform) { Requires.NotNull(source, nameof(source)); Requires.NotNull(transform, nameof(transform)); IPropagatorBlock <IProjectVersionedValue <TInput>, TOut> transformBlock = DataflowBlockSlim.CreateTransformBlock(transform, skipIntermediateInputData: true, skipIntermediateOutputData: true); IDisposable link = source.LinkTo(transformBlock, DataflowOption.PropagateCompletion); return(new DisposableValue <ISourceBlock <TOut> >(transformBlock, link)); }
public IDisposable RegisterSnapshotProvider(DependenciesSnapshotProvider 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 DependenciesSnapshotProvider provider)) { _snapshotProviderByPath = _snapshotProviderByPath.Remove(e.OldFullPath); if (!string.IsNullOrEmpty(e.NewFullPath)) { _snapshotProviderByPath = _snapshotProviderByPath.SetItem(e.NewFullPath, provider); } } } } }
protected override void Initialize() { base.Initialize(); _broadcastBlock = DataflowBlockSlim.CreateBroadcastBlock <IProjectVersionedValue <T> >(null); }
/// <summary> /// Generates the original references directory tree. /// </summary> protected override void Initialize() { #pragma warning disable RS0030 // symbol LoadedProject is banned using (UnconfiguredProjectAsynchronousTasksService.LoadedProject()) { base.Initialize(); // this.IsApplicable may take a project lock, so we can't do it inline with this method // which is holding a private lock. It turns out that doing it asynchronously isn't a problem anyway, // so long as we guard against races with the Dispose method. UnconfiguredProjectAsynchronousTasksService.LoadedProjectAsync( async delegate { await TaskScheduler.Default.SwitchTo(alwaysYield: true); UnconfiguredProjectAsynchronousTasksService .UnloadCancellationToken.ThrowIfCancellationRequested(); lock (SyncObject) { Verify.NotDisposed(this); // Issue this token before hooking the SnapshotChanged event to prevent a race // where a snapshot tree is replaced by the initial, empty tree created below. // The handler will cancel this token before submitting its update. CancellationToken initialTreeCancellationToken = _treeUpdateCancellationSeries.CreateNext(); _ = SubmitTreeUpdateAsync( delegate { IProjectTree dependenciesNode = CreateDependenciesFolder(); return(Task.FromResult(new TreeUpdateResult(dependenciesNode))); }, initialTreeCancellationToken); ITargetBlock <SnapshotChangedEventArgs> actionBlock = DataflowBlockSlim.CreateActionBlock <SnapshotChangedEventArgs>( e => OnDependenciesSnapshotChanged(_dependenciesSnapshotProvider, e), "DependenciesProjectTreeProviderSource {1}", skipIntermediateInputData: true); _snapshotEventListener = _dependenciesSnapshotProvider.SnapshotChangedSource.LinkTo(actionBlock, new DataflowLinkOptions() { PropagateCompletion = true }); } }, registerFaultHandler: true); } #pragma warning restore RS0030 // symbol LoadedProject is banned IProjectTree CreateDependenciesFolder() { var values = new ReferencesProjectTreeCustomizablePropertyValues { Caption = Resources.DependenciesNodeName, Icon = ManagedImageMonikers.ReferenceGroup.ToProjectSystemType(), ExpandedIcon = ManagedImageMonikers.ReferenceGroup.ToProjectSystemType(), Flags = DependencyTreeFlags.DependenciesRootNodeFlags }; // Allow property providers to perform customization. // These are ordered from lowest priority to highest, allowing higher priority // providers to override lower priority providers. foreach (IProjectTreePropertiesProvider provider in _projectTreePropertiesProviders.ExtensionValues()) { provider.CalculatePropertyValues(null, values); } // Note that all the parameters are specified so we can force this call to an // overload of NewTree available prior to 15.5 versions of CPS. Once a 15.5 build // is publicly available we can move this to an overload with default values for // most of the parameters, and we'll only need to pass the interesting ones. return(NewTree( caption: values.Caption, filePath: null, browseObjectProperties: null, icon: values.Icon, expandedIcon: values.ExpandedIcon, visible: true, flags: values.Flags)); } }
protected override void Initialize() { // Create our broadcast block for subscribers to get new ILaunchProfiles Information _broadcastBlock = DataflowBlockSlim.CreateBroadcastBlock <ILaunchSettings>(); _changedSourceBlock = _broadcastBlock.SafePublicize(); // Subscribe to changes to the broadcast block using the idle scheduler. This should filter out a lot of the intermediates // states that files can be in. if (_projectSubscriptionService != null) { // The use of AsyncLazy with dataflow can allow state stored in the execution context to leak through. The downstream affect is // calls to say, get properties, may fail. To avoid this, we capture the execution context here, and it will be reapplied when // we get new subscription data from the dataflow. ITargetBlock <IProjectVersionedValue <Tuple <IProjectSubscriptionUpdate, IProjectCapabilitiesSnapshot> > > projectChangesBlock = DataflowBlockSlim.CreateActionBlock( DataflowUtilities.CaptureAndApplyExecutionContext <IProjectVersionedValue <Tuple <IProjectSubscriptionUpdate, IProjectCapabilitiesSnapshot> > >(ProjectRuleBlock_ChangedAsync)); StandardRuleDataflowLinkOptions evaluationLinkOptions = DataflowOption.WithRuleNames(ProjectDebugger.SchemaName); _projectRuleSubscriptionLink = ProjectDataSources.SyncLinkTo( _projectSubscriptionService.ProjectRuleSource.SourceBlock.SyncLinkOptions(evaluationLinkOptions), _commonProjectServices.Project.Capabilities.SourceBlock.SyncLinkOptions(), projectChangesBlock, linkOptions: DataflowOption.PropagateCompletion); } // Make sure we are watching the file at this point WatchLaunchSettingsFile(); }
protected override void Initialize() { IPropagatorBlock <IProjectVersionedValue <ILaunchSettings>, IProjectVersionedValue <IReadOnlyList <IEnumValue> > > debugProfilesBlock = DataflowBlockSlim.CreateTransformBlock <IProjectVersionedValue <ILaunchSettings>, IProjectVersionedValue <IReadOnlyList <IEnumValue> > >( update => { // Compute the new enum values from the profile provider var generatedResult = DebugProfileEnumValuesGenerator.GetEnumeratorEnumValues(update.Value).ToImmutableList(); _dataSourceVersion++; ImmutableDictionary <NamedIdentity, IComparable> dataSources = ImmutableDictionary <NamedIdentity, IComparable> .Empty.Add(DataSourceKey, DataSourceVersion); return(new ProjectVersionedValue <IReadOnlyList <IEnumValue> >(generatedResult, dataSources)); }); IBroadcastBlock <IProjectVersionedValue <IReadOnlyList <IEnumValue> > > broadcastBlock = DataflowBlockSlim.CreateBroadcastBlock <IProjectVersionedValue <IReadOnlyList <IEnumValue> > >(); // The interface has two definitions of SourceBlock: one from // ILaunchSettingsProvider, and one from IProjectValueDataSource<T> (via // IVersionedLaunchSettingsProvider). We need the cast to pick the proper one. _launchProfileProviderLink = ((IProjectValueDataSource <ILaunchSettings>)LaunchSettingProvider).SourceBlock.LinkTo( debugProfilesBlock, linkOptions: DataflowOption.PropagateCompletion); JoinUpstreamDataSources(LaunchSettingProvider); _debugProviderLink = debugProfilesBlock.LinkTo(broadcastBlock, DataflowOption.PropagateCompletion); _publicBlock = broadcastBlock.SafePublicize(); }
private void SubscribeToConfiguredProject( ConfiguredProject configuredProject, IProjectSubscriptionService subscriptionService, IReadOnlyCollection <string> watchedEvaluationRules, IReadOnlyCollection <string> watchedDesignTimeBuildRules) { // Use intermediate buffer blocks for project rule data to allow subsequent blocks // to only observe specific rule name(s). var intermediateBlockDesignTime = new BufferBlock <IProjectVersionedValue <IProjectSubscriptionUpdate> >( new ExecutionDataflowBlockOptions() { NameFormat = "CrossTarget Intermediate DesignTime Input: {1}" }); var intermediateBlockEvaluation = new BufferBlock <IProjectVersionedValue <IProjectSubscriptionUpdate> >( new ExecutionDataflowBlockOptions() { NameFormat = "CrossTarget Intermediate Evaluation Input: {1}" }); _subscriptions ??= new DisposableBag(); _subscriptions.AddDisposable( subscriptionService.JointRuleSource.SourceBlock.LinkTo( intermediateBlockDesignTime, ruleNames: watchedDesignTimeBuildRules.Union(watchedEvaluationRules), suppressVersionOnlyUpdates: true, linkOptions: DataflowOption.PropagateCompletion)); _subscriptions.AddDisposable( subscriptionService.ProjectRuleSource.SourceBlock.LinkTo( intermediateBlockEvaluation, ruleNames: watchedEvaluationRules, suppressVersionOnlyUpdates: true, linkOptions: DataflowOption.PropagateCompletion)); ITargetBlock <IProjectVersionedValue <Tuple <IProjectSubscriptionUpdate, IProjectCatalogSnapshot, IProjectCapabilitiesSnapshot> > > actionBlockDesignTimeBuild = DataflowBlockSlim.CreateActionBlock <IProjectVersionedValue <Tuple <IProjectSubscriptionUpdate, IProjectCatalogSnapshot, IProjectCapabilitiesSnapshot> > >( e => OnProjectChangedAsync(e.Value.Item1, e.Value.Item2, e.Value.Item3, configuredProject, RuleHandlerType.DesignTimeBuild), new ExecutionDataflowBlockOptions() { NameFormat = "CrossTarget DesignTime Input: {1}" }); ITargetBlock <IProjectVersionedValue <Tuple <IProjectSubscriptionUpdate, IProjectCatalogSnapshot, IProjectCapabilitiesSnapshot> > > actionBlockEvaluation = DataflowBlockSlim.CreateActionBlock <IProjectVersionedValue <Tuple <IProjectSubscriptionUpdate, IProjectCatalogSnapshot, IProjectCapabilitiesSnapshot> > >( e => OnProjectChangedAsync(e.Value.Item1, e.Value.Item2, e.Value.Item3, configuredProject, RuleHandlerType.Evaluation), new ExecutionDataflowBlockOptions() { NameFormat = "CrossTarget Evaluation Input: {1}" }); _subscriptions.AddDisposable(ProjectDataSources.SyncLinkTo( intermediateBlockDesignTime.SyncLinkOptions(), subscriptionService.ProjectCatalogSource.SourceBlock.SyncLinkOptions(), configuredProject.Capabilities.SourceBlock.SyncLinkOptions(), actionBlockDesignTimeBuild, linkOptions: DataflowOption.PropagateCompletion)); _subscriptions.AddDisposable(ProjectDataSources.SyncLinkTo( intermediateBlockEvaluation.SyncLinkOptions(), subscriptionService.ProjectCatalogSource.SourceBlock.SyncLinkOptions(), configuredProject.Capabilities.SourceBlock.SyncLinkOptions(), actionBlockEvaluation, linkOptions: DataflowOption.PropagateCompletion)); }
protected override void Initialize() { base.Initialize(); // Create an action block to process the design time inputs and configuration general changes ITargetBlock <IProjectVersionedValue <Tuple <DesignTimeInputs, IProjectSubscriptionUpdate> > > inputsAction = DataflowBlockSlim.CreateActionBlock <IProjectVersionedValue <Tuple <DesignTimeInputs, IProjectSubscriptionUpdate> > >(ProcessDataflowChanges); _broadcastBlock = DataflowBlockSlim.CreateBroadcastBlock <IProjectVersionedValue <DesignTimeInputsDelta> >(nameFormat: nameof(DesignTimeInputsChangeTracker) + "Broadcast {1}"); _publicBlock = AllowSourceBlockCompletion ? _broadcastBlock : _broadcastBlock.SafePublicize(); IDisposable projectLink = ProjectDataSources.SyncLinkTo( _inputsDataSource.SourceBlock.SyncLinkOptions( linkOptions: DataflowOption.PropagateCompletion), _projectSubscriptionService.ProjectRuleSource.SourceBlock.SyncLinkOptions( linkOptions: DataflowOption.WithRuleNames(ConfigurationGeneral.SchemaName)), inputsAction, DataflowOption.PropagateCompletion, cancellationToken: _project.Services.ProjectAsynchronousTasks.UnloadCancellationToken); // Create an action block to process file change notifications ITargetBlock <IProjectVersionedValue <string[]> > fileWatcherAction = DataflowBlockSlim.CreateActionBlock <IProjectVersionedValue <string[]> >(ProcessFileChangeNotification); IDisposable watcherLink = _fileWatcher.SourceBlock.LinkTo(fileWatcherAction, DataflowOption.PropagateCompletion); _disposables.Add(projectLink); _disposables.Add(watcherLink); JoinUpstreamDataSources(_inputsDataSource, _projectSubscriptionService.ProjectRuleSource, _fileWatcher); }