public ProjectData RemoveTaskLink(TaskLink taskLink) { Debug.Assert(taskLink != null); if (!TaskMap.ContainsKey(taskLink.PredecessorId) || !TaskMap.ContainsKey(taskLink.SuccessorId)) { return(this); } var project = this; var predescessorLinks = Get(TaskFields.PredecessorLinks, taskLink.SuccessorId); predescessorLinks = predescessorLinks.Remove(taskLink); var successorLinks = Get(TaskFields.SuccessorLinks, taskLink.PredecessorId); successorLinks = successorLinks.Remove(taskLink); project = project.SetRaw(TaskFields.PredecessorLinks, taskLink.SuccessorId, predescessorLinks) .SetRaw(TaskFields.SuccessorLinks, taskLink.PredecessorId, successorLinks) .Reset(TaskFields.Predecessors, taskLink.SuccessorId) .Reset(TaskFields.Successors, taskLink.PredecessorId); return(project); }
public Project AddTaskLink(TaskLink taskLink) { if (taskLink == null) { throw new ArgumentNullException(nameof(taskLink)); } var predecessor = GetTask(taskLink.PredecessorId); if (predecessor == null) { throw new ArgumentException($"The project doesn't contain a task with the predecessor ID {taskLink.PredecessorId}", nameof(taskLink)); } var successor = GetTask(taskLink.SuccessorId); if (successor == null) { throw new ArgumentException($"The project doesn't contain a task with the successor ID {taskLink.SuccessorId}", nameof(taskLink)); } if (Data.TaskLinkCausesCycle(taskLink)) { throw new InvalidOperationException($"Cannot add a link from task {predecessor.Ordinal} to task {successor.Ordinal} as this would cause a cycle."); } var project = Data.AddTaskLink(taskLink); return(UpdateProject(project)); }
public Project AddTaskLink(TaskId predecessorId, TaskId successorId, TaskLinkType type = TaskLinkType.FinishToStart, TimeSpan lag = default) { var taskLink = TaskLink.Create(predecessorId, successorId, type, lag); return(AddTaskLink(taskLink)); }
public bool TaskLinkCausesCycle(TaskLink taskLink) { Debug.Assert(taskLink != null); if (!TaskMap.ContainsKey(taskLink.PredecessorId) || !TaskMap.ContainsKey(taskLink.SuccessorId)) { return(false); } IEnumerable <TaskId> GetPredecessors(TaskId successorId) { var links = Get(TaskFields.PredecessorLinks, successorId); foreach (var link in links) { yield return(link.PredecessorId); } if (successorId == taskLink.SuccessorId) { yield return(taskLink.PredecessorId); } } var queue = new Queue <TaskId>(); foreach (var id in Tasks) { foreach (var predecessorId in GetPredecessors(id)) { queue.Enqueue(predecessorId); while (queue.Count > 0) { var taskId = queue.Dequeue(); if (taskId == id) { return(true); } foreach (var pid in GetPredecessors(taskId)) { queue.Enqueue(pid); } if (taskId == taskLink.SuccessorId) { queue.Enqueue(taskLink.PredecessorId); } } } } return(false); }
public Project RemoveTaskLink(TaskLink taskLink) { if (taskLink == null) { throw new ArgumentNullException(nameof(taskLink)); } var project = Data.RemoveTaskLink(taskLink); return(UpdateProject(project)); }
private static TimeSpan GetFreeSlack(ProjectData project, TaskLink link) { var calendar = project.Information.Calendar; var projectStart = project.Information.Start; var predecessorEarlyStart = project.Get(TaskFields.EarlyStart, link.PredecessorId); var predecessorEarlyFinish = project.Get(TaskFields.EarlyFinish, link.PredecessorId); var successorEarlyStart = project.Get(TaskFields.EarlyStart, link.SuccessorId); var successorEarlyFinish = project.Get(TaskFields.EarlyFinish, link.SuccessorId); switch (link.Type) { case TaskLinkType.FinishToStart: return(calendar.GetWork(predecessorEarlyFinish, successorEarlyStart) - link.Lag); case TaskLinkType.StartToStart: return(calendar.GetWork(predecessorEarlyStart, successorEarlyStart) - link.Lag); case TaskLinkType.FinishToFinish: { // Finish-to-Finish is special in that it won't schedule tasks before the project's // start date. Thus, we'll ignore the lag if the successor was supposed to end before // us (i.e. had a negative slack) but ended up ending after ours (i.e. it couldn't // start any earlier). var successorCannotStartAnyEarlier = successorEarlyStart == projectStart && successorEarlyFinish > predecessorEarlyFinish && link.Lag < TimeSpan.Zero; if (successorCannotStartAnyEarlier) { return(calendar.GetWork(predecessorEarlyFinish, successorEarlyFinish)); } return(calendar.GetWork(predecessorEarlyFinish, successorEarlyFinish) - link.Lag); } case TaskLinkType.StartToFinish: return(calendar.GetWork(predecessorEarlyStart, successorEarlyFinish) - link.Lag); default: throw new Exception($"Unexpected case label {link.Type}"); } }
public ProjectData AddTaskLink(TaskLink taskLink) { Debug.Assert(taskLink != null); Debug.Assert(GetTaskLink(taskLink.PredecessorId, taskLink.SuccessorId) == null); Debug.Assert(!TaskLinkCausesCycle(taskLink)); var project = this; var predescessorLinks = Get(TaskFields.PredecessorLinks, taskLink.SuccessorId); predescessorLinks = InsertTaskLink(predescessorLinks, taskLink, isPredecessor: true); var successorLinks = Get(TaskFields.SuccessorLinks, taskLink.PredecessorId); successorLinks = InsertTaskLink(successorLinks, taskLink, isPredecessor: false); project = project.SetRaw(TaskFields.PredecessorLinks, taskLink.SuccessorId, predescessorLinks) .SetRaw(TaskFields.SuccessorLinks, taskLink.PredecessorId, successorLinks) .Reset(TaskFields.Predecessors, taskLink.SuccessorId) .Reset(TaskFields.Successors, taskLink.PredecessorId); return(project); }
private static ProjectData SetTaskPredecessorsOrSuccessors(ProjectData project, TaskId id, string value, bool isSuccessors) { var linkField = isSuccessors ? TaskFields.SuccessorLinks : TaskFields.PredecessorLinks; value = value.Trim(); var predecessorsBuilder = ImmutableArray.CreateBuilder <TaskId>(); var remainingTaskLinks = project.Get(linkField, id).ToList(); if (value.Length > 0) { var parts = value.Split(','); foreach (var part in parts) { var partText = part.Trim(); var taskText = partText; var sign = Math.Max(partText.IndexOf('+'), partText.IndexOf('-')); var lag = TimeSpan.Zero; if (sign >= 0) { var lagText = partText.Substring(sign).Trim(); lag = project.Information.TimeConversion.ParseDuration(lagText); taskText = partText.Substring(0, sign).Trim(); } var taskOrdinalText = taskText; var linkType = (TaskLinkType?)null; if (taskText.EndsWith("FS", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.FinishToStart; } else if (taskText.EndsWith("SS", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.StartToStart; } else if (taskText.EndsWith("FF", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.FinishToFinish; } else if (taskText.EndsWith("SF", StringComparison.OrdinalIgnoreCase)) { linkType = TaskLinkType.StartToFinish; } if (linkType != null) { taskOrdinalText = taskText.Substring(0, taskText.Length - 2).Trim(); } else if (sign < 0) { linkType = TaskLinkType.FinishToStart; } else { throw new FormatException($"'{partText}' isn't a valid value"); } if (!int.TryParse(taskOrdinalText, out var taskOrdinal)) { throw new FormatException($"'{taskOrdinalText}' isn't a valid int"); } var taskId = project.GetTask(taskOrdinal); if (taskId.IsDefault) { throw new FormatException($"'{taskOrdinal}' isn't a valid task"); } var predecessorId = isSuccessors ? id : taskId; var successorId = isSuccessors ? taskId : id; var taskLink = TaskLink.Create(predecessorId, successorId, linkType.Value, lag); var existingLink = project.GetTaskLink(predecessorId, successorId); if (existingLink != null) { project = project.RemoveTaskLink(existingLink); } else if (project.TaskLinkCausesCycle(taskLink)) { var predecessorOrdinal = project.Get(TaskFields.Ordinal, predecessorId); var successorOrdinal = project.Get(TaskFields.Ordinal, successorId); throw new InvalidOperationException($"Cannot add a link from task {predecessorOrdinal} to task {successorOrdinal} as this would cause a cycle."); } project = project.AddTaskLink(taskLink); remainingTaskLinks.Remove(taskLink); } } foreach (var taskLink in remainingTaskLinks) { project = project.RemoveTaskLink(taskLink); } return(project); }
private static void ComputeLateStartAndFinish(ProjectData project, TaskId taskId, TaskLink successorLink, out DateTimeOffset lateStart, out DateTimeOffset lateFinish) { var calendar = project.Information.Calendar; switch (successorLink.Type) { case TaskLinkType.FinishToStart: lateFinish = project.Get(TaskFields.LateStart, successorLink.SuccessorId); break; case TaskLinkType.StartToStart: lateStart = project.Get(TaskFields.LateStart, successorLink.SuccessorId); ComputeFinish(project, taskId, ref lateStart, out lateFinish); break; case TaskLinkType.FinishToFinish: lateFinish = project.Get(TaskFields.LateFinish, successorLink.SuccessorId); break; case TaskLinkType.StartToFinish: lateStart = project.Get(TaskFields.LateFinish, successorLink.SuccessorId); ComputeFinish(project, taskId, ref lateStart, out lateFinish); break; default: throw new Exception($"Unexpected case label {successorLink.Type}"); } if (successorLink.Lag != TimeSpan.Zero) { lateFinish = project.Information.Calendar.AddWork(lateFinish, -successorLink.Lag); } // TODO: Is this correct? We probably need to align with with ComputeEarlyStart. if (lateFinish > project.Information.Finish) { lateFinish = project.Information.Finish; } ComputeStart(project, taskId, out lateStart, ref lateFinish); }
private static void ComputeEarlyStartAndFinish(ProjectData project, TaskId taskId, TaskLink predecessorLink, out DateTimeOffset earlyStart, out DateTimeOffset earlyFinish) { var calendar = project.Information.Calendar; var type = predecessorLink.Type; var predecessorEarlyStart = project.Get(TaskFields.EarlyStart, predecessorLink.PredecessorId); var predecessorEarlyFinish = project.Get(TaskFields.EarlyFinish, predecessorLink.PredecessorId); switch (type) { case TaskLinkType.FinishToStart: earlyStart = predecessorEarlyFinish; break; case TaskLinkType.StartToStart: earlyStart = predecessorEarlyStart; break; case TaskLinkType.FinishToFinish: earlyFinish = predecessorEarlyFinish; ComputeStart(project, taskId, out earlyStart, ref earlyFinish); break; case TaskLinkType.StartToFinish: earlyFinish = predecessorEarlyStart; ComputeStart(project, taskId, out earlyStart, ref earlyFinish); break; default: throw new Exception($"Unexpected case label {type}"); } if (predecessorLink.Lag != TimeSpan.Zero) { earlyStart = project.Information.Calendar.AddWork(earlyStart, predecessorLink.Lag); } var mustSnap = type == TaskLinkType.FinishToFinish; if (mustSnap && earlyStart < project.Information.Start) { earlyStart = project.Information.Start; } ComputeFinish(project, taskId, ref earlyStart, out earlyFinish); // In case of Start-to-Finish, the end date is snapped to a start time // unless there is positive lag. if (type == TaskLinkType.StartToFinish && predecessorLink.Lag <= TimeSpan.Zero) { earlyFinish = calendar.FindWorkStart(earlyFinish); } }
private ImmutableArray <TaskLink> InsertTaskLink(ImmutableArray <TaskLink> taskLinks, TaskLink taskLink, bool isPredecessor) { var newId = isPredecessor ? taskLink.PredecessorId : taskLink.SuccessorId; var newOrdinal = Get(TaskFields.Ordinal, newId); for (var i = 0; i < taskLinks.Length; i++) { var existingId = isPredecessor ? taskLinks[i].PredecessorId : taskLinks[i].SuccessorId; var existingOrdinal = Get(TaskFields.Ordinal, existingId); if (newOrdinal > existingOrdinal) { return(taskLinks.Insert(i, taskLink)); } } return(taskLinks.Add(taskLink)); }