/// <summary> /// Assigns the data cooker to the appropriate pass(es) and block(s). /// </summary> /// <param name="scheduler"> /// The scheduler in which this node participates. /// </param> /// <param name="dependentPass"> /// The pass in which the dependent cooker that resulted in this call to <see cref="Schedule"/> runs. /// This parameter may be null. /// </param> internal override void Schedule(ISourceDataCookerScheduler scheduler, SchedulingPass dependentPass) { if (dependentPass == null) { // An AsRequired cooker should never be scheduled by itself - but only when required by // another cooker. // return; } if (this.Passes.Contains(dependentPass)) { // Already been scheduled for this pass return; } // change the starting block to match the block of the dependent cooker this.Pass = dependentPass; this.SetStatus(SchedulingStatus.NotScheduled); base.Schedule(scheduler, null); Debug.Assert(this.Status == SchedulingStatus.Scheduled); Debug.Assert(this.Pass != null); Debug.Assert(this.Block != null); this.Passes.Add(dependentPass); }
/// <summary> /// Assigns <see cref="PassIndex"/> and <see cref="PassBlockIndex"/>. /// </summary> /// <param name="scheduler"> /// The scheduler in which this node participates. /// </param> /// <param name="dependentPass"> /// The pass in which the dependent cooker that resulted in this call to <see cref="Schedule"/> runs. /// This parameter may be null. /// </param> internal virtual void Schedule(ISourceDataCookerScheduler scheduler, SchedulingPass dependentPass) { Debug.Assert(!(scheduler is null)); if (this.Status == SchedulingStatus.Scheduled) { return; } if (this.Status == SchedulingStatus.Scheduling) { throw new InvalidOperationException( $"Source data cooker {nameof(this.DataCookerPath)} is involved in a circular dependency."); } this.Status = SchedulingStatus.Scheduling; // An AsRequired node will establish this before scheduling if (this.Pass == null) { this.Pass = scheduler.Pass0; } this.Block = this.Pass.Block0; var requiredCookerPaths = this.CookerDependencies.RequiredDataCookers ?? new List <DataCookerPath>(); List <DataCookerSchedulingNode> dependencies; { // Sort dependencies so that AsRequired are done last. These need to know the final PassIndex // of this cooker before they can be scheduled. // var normalDependencies = new List <DataCookerSchedulingNode>(); var asRequiredDependencies = new List <DataCookerSchedulingNode>(); foreach (var requiredDataCookerPath in requiredCookerPaths) { var sourceParserId = requiredDataCookerPath.SourceParserId; if (!StringComparer.Ordinal.Equals(sourceParserId, this.DataCooker.Path.SourceParserId)) { throw new InvalidOperationException( $"A source data parser may not require data cookers from other source parsers: {this.DataCookerPath}"); } var requiredCookerNode = scheduler.GetSchedulingNode(requiredDataCookerPath); Debug.Assert(!(requiredCookerNode is null)); var productionStrategy = requiredCookerNode.SourceDataCookerDescriptor.DataProductionStrategy; if (productionStrategy == DataProductionStrategy.AsRequired) { asRequiredDependencies.Add(requiredCookerNode); } else { normalDependencies.Add(requiredCookerNode); } } if (this.SourceDataCookerDescriptor.DataProductionStrategy == DataProductionStrategy.AsRequired) { if (normalDependencies.Any()) { throw new InvalidOperationException( $"A source data cooker with a {nameof(DataProductionStrategy)} " + $"of {nameof(DataProductionStrategy.AsRequired)} cannot depend on source data " + $"cookers not using this same {nameof(DataProductionStrategy)}: {this.DataCooker.Path} " + $"depends on {normalDependencies[0].DataCookerPath}"); } } dependencies = normalDependencies.Concat(asRequiredDependencies).ToList(); } // Make sure all of the dependencies are scheduled. foreach (var requiredCookerNode in dependencies) { requiredCookerNode.Schedule(scheduler, this.Pass); if (this.PassIndex > requiredCookerNode.PassIndex) { continue; } if (this.CookerDependencies.DependencyTypes is null || !this.CookerDependencies.DependencyTypes.TryGetValue( requiredCookerNode.DataCookerPath, out var dependencyType)) { dependencyType = DataCookerDependencyType.AlignedWithProductionStrategy; } var productionStrategy = requiredCookerNode.SourceDataCookerDescriptor.DataProductionStrategy; if (productionStrategy == DataProductionStrategy.AsRequired) { if (dependencyType != DataCookerDependencyType.AlignedWithProductionStrategy) { // These source cookers will automatically run in any/all stages where a dependent source // cooker is scheduled. Given that, only DataCookerDependencyType.AlignedWithProductionStrategy // make sense. // throw new InvalidOperationException( $"A SourceCooker whose {nameof(DataProductionStrategy)} is " + $"{nameof(DataProductionStrategy.AsRequired)} can only be consumed using " + $"{nameof(DataCookerDependencyType.AlignedWithProductionStrategy)}"); } } var oldPassIndex = this.Pass.Index; if (requiredCookerNode.PassIndex > this.PassIndex) { this.Pass = requiredCookerNode.Pass; } if (productionStrategy == DataProductionStrategy.PostSourceParsing) { if (dependencyType == DataCookerDependencyType.AlignedWithProductionStrategy) { if (requiredCookerNode.PassIndex == int.MaxValue) { throw new InvalidOperationException( "The number of required passes through the data source exceeds maximum allowed value."); } if (requiredCookerNode.Pass.Next == null) { requiredCookerNode.Pass.CreateNext(); } Debug.Assert(requiredCookerNode.Pass.Next != null); // this cannot take place in the same pass as the required data cooker if (requiredCookerNode.Pass.Next.Index > this.PassIndex) { this.Pass = requiredCookerNode.Pass.Next; } } } if (this.PassIndex > oldPassIndex) { // If we've moved into a higher source pass, then the block // should reset to zero, or we could end up in an invalid state. // this.Block = this.Pass.Block0; } if (this.PassIndex == requiredCookerNode.PassIndex) { // This should be set at least to the requiredCooker, in case that cooker has a // required cooker. // if (this.PassBlockIndex < requiredCookerNode.PassBlockIndex) { this.Block = requiredCookerNode.Block; } if (dependencyType == DataCookerDependencyType.AsConsumed || productionStrategy == DataProductionStrategy.AsRequired || (dependencyType == DataCookerDependencyType.AlignedWithProductionStrategy && productionStrategy == DataProductionStrategy.AsConsumed)) { // if this takes place in the same pass as the required cooker, just make it come after // the required cooker in the pass // if (requiredCookerNode.Block.Next == null) { this.Block = requiredCookerNode.Block.CreateNext(); } else if (this.PassBlockIndex < requiredCookerNode.Block.Next.Index) { this.Block = requiredCookerNode.Block.Next; } } } } this.Block.Nodes.Add(this); this.Status = SchedulingStatus.Scheduled; }