using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Braincase.GanttChart { /// /// Wrapper ProjectManager class /// [Serializable] public class ProjectManager : ProjectManager { } /// /// Concrete ProjectManager class for the IProjectManager interface /// /// /// [Serializable] public class ProjectManager : IProjectManager where T : Task where R : class { HashSet _mRegister = new HashSet(); List _mRootTasks = new List(); Dictionary> _mTaskGroups = new Dictionary>(); Dictionary> _mDependents = new Dictionary>(); Dictionary> _mResources = new Dictionary>(); Dictionary> _mSplitTasks = new Dictionary>(); Dictionary _mSplitTaskOfPart = new Dictionary(); Dictionary _mParentOfChild = new Dictionary(); Dictionary _mTaskIndices = new Dictionary(); /// /// Create a new Project /// public ProjectManager() { Now = 0; Start = DateTime.Now; TimeScale = GanttChart.TimeScale.Day; } /// /// Get or set the period we are at now /// public int Now { get; set; } /// /// 当前索引 /// public int NowIndex { get; set; } /// /// 当前坐标位置 /// public float NowIndexXf; /// /// 时间间隔 1,0.5 0.2 0.1,0.01 等 /// public float TimeInterval = 0.1f; /// /// 工艺集合的最大时间,显示的坐标值。 /// public float MaxCountLenth; /// /// 最大播放屏幕坐标长度, /// public float MaxPlayLenth; /// /// 总播放帧数 /// public int AllCountFrame; /// /// 播放倍数 /// public int PlayeTime = 1; /// /// 画播放标志 /// public float DrawPlayLineMask = 5; /// /// 是否播放 /// public bool IsPlaye = true; /// /// 是否播放 /// public bool IsLastPlaye = false; /// /// Get or set the period we are at now wangdeqquan 20200531 当前开始序号 /// public int StartIndex { get; set; } //数据扩到 100倍,用整数代表小数 public float ValueScale = 1.5f; /// /// Get or set the starting date for this project /// public DateTime Start { get; set; } /// /// Get or set the time scale on this project. Each period on the task represents one unit of TimeScale. /// public TimeScale TimeScale { get; set; } /// /// Get the date after the specified period based on TimeScale /// /// /// public DateTime GetDateTime(int period) { DateTime datetime = DateTime.Now; if (this.TimeScale == TimeScale.Day) { datetime = this.Start.AddDays(period); } else if (this.TimeScale == TimeScale.Week) { datetime = this.Start.AddDays(period * 7 - (int) this.Start.DayOfWeek); } return datetime; } /// /// Create a new T for this Project and add it to the T tree /// /// public void Add(T task) { if (!this._mRegister.Contains(task)) { _mRegister.Add(task); _mRootTasks.Add(task); _mTaskGroups[task] = new List(); _mDependents[task] = new HashSet(); _mResources[task] = new HashSet(); _mParentOfChild[task] = null; } } /// /// Remove task from this Project /// /// public void Delete(T task) { if (task != null && !_mSplitTaskOfPart.ContainsKey(task) // not a task part ) { // Check if is group so can ungroup the task if (this.IsGroup(task)) this.Ungroup(task); if (this.IsSplit(task)) this.Merge(task); // Really delete all references _mRootTasks.Remove(task); _mTaskGroups.Remove(task); _mDependents.Remove(task); _mResources.Remove(task); _mParentOfChild.Remove(task); _mSplitTasks.Remove(task); foreach (var g in _mTaskGroups) g.Value.Remove(task); // optimised: no need to check for contains foreach (var g in _mDependents) g.Value.Remove(task); _mRegister.Remove(task); } else if (task != null && _mSplitTaskOfPart.ContainsKey(task) // must be existing part ) { var split = _mSplitTaskOfPart[task]; var parts = _mSplitTasks[split]; if (parts.Count > 2) { parts.Remove(task); // remove the part from the split task _mRegister.Remove(task); // unregister the part _mResources.Remove(task); _mSplitTaskOfPart.Remove(task); // remove the reverse lookup split.Start = parts.First().Start; // recalculate the split task split.End = parts.Last().End; split.Duration = split.End - split.Start; } else { this.Merge(split); } } } /// /// Add the member T to the group T /// /// /// public void Group(T group, T member) { if (group != null && member != null && _mRegister.Contains(group) ) { // change the member to become the split task is member is a task part if (_mSplitTaskOfPart.ContainsKey(member)) member = _mSplitTaskOfPart[member]; if (_mRegister.Contains(member) && !group.Equals(member) && !_mSplitTasks.ContainsKey(group) // group cannot be split task && !_mSplitTaskOfPart.ContainsKey(group) // group cannot be parts && !this.DecendantsOf(member).Contains(group) && !this.HasRelations(group) ) { _LeaveParent(member); _mTaskGroups[group].Add(member); _mParentOfChild[member] = group; _RecalculateAncestorsSchedule(); _RecalculateSlack(); // clear indices since positions changed _mTaskIndices.Clear(); } } } /// /// Remove the member task from its group /// public void Ungroup(T group, T member) { if (group != null && member != null && _mRegister.Contains(group) ) { // change the member to become the split task is member is a task part if (_mSplitTaskOfPart.ContainsKey(member)) member = _mSplitTaskOfPart[member]; if (_mRegister.Contains(member) && this.IsGroup(group)) { var ancestor = this.AncestorsOf(group).LastOrDefault(); if (ancestor == null) // group is in root _mRootTasks.Insert(_mRootTasks.IndexOf(group) + 1, member); else // group is not in root, we get the ancestor that is in root _mRootTasks.Insert(_mRootTasks.IndexOf(ancestor) + 1, member); _mTaskGroups[group].Remove(member); _mParentOfChild[member] = null; _RecalculateAncestorsSchedule(); } } } /// /// Ungroup all member task under the specfied group task. The specified group task will become a normal task. /// /// public void Ungroup(T group) { List list; if (group != null //&& _mRegister.Contains(group) && _mTaskGroups.TryGetValue(group, out list)) { var newgroup = this.ParentOf(group); if (newgroup == null) { foreach (var member in list) { _mRootTasks.Add(member); _mParentOfChild[member] = null; } } else { foreach (var member in list) { _mTaskGroups[newgroup].Add(member); _mParentOfChild[member] = null; } } list.Clear(); _RecalculateAncestorsSchedule(); } } /// /// Get the zero-based index of the task in this Project /// /// /// public int IndexOf(T task) { if (_mRegister.Contains(task)) { if (_mTaskIndices.ContainsKey(task)) return (int) _mTaskIndices[task]; int i = 0; foreach (var x in Tasks) { if (x.Equals(task)) { _mTaskIndices[task] = i; return i; } i++; } } return -1; } /// /// Re-position the task by offset amount of places /// /// /// public void Move(T task, int offset) { if (task != null && _mRegister.Contains(task) && offset != 0) { int indexoftask = IndexOf(task); if (indexoftask > -1) { int newindexoftask = indexoftask + offset; // check for out of index bounds if (newindexoftask < 0) newindexoftask = 0; else if (newindexoftask > Tasks.Count()) newindexoftask = Tasks.Count(); // get the index of the task that will be displaced var displacedtask = Tasks.ElementAtOrDefault(newindexoftask); if (displacedtask == null) { // adding to the end of the task list _LeaveParent(task); _mRootTasks.Add(task); // clear indices since positions changed _mTaskIndices.Clear(); } else if (!displacedtask.Equals(task)) { int indexofdestinationtask; var displacedtaskparent = this.ParentOf(displacedtask); if (displacedtaskparent == null) // displacedtask is in root { indexofdestinationtask = _mRootTasks.IndexOf(displacedtask); _LeaveParent(task); _mRootTasks.Insert(indexofdestinationtask, task); } else if (!displacedtaskparent.Equals(task)) // displaced task is not under the moving task { var memberlist = _mTaskGroups[displacedtaskparent]; indexofdestinationtask = memberlist.IndexOf(displacedtask); _LeaveParent(task); memberlist.Insert(indexofdestinationtask, task); _mParentOfChild[task] = displacedtaskparent; } // clear indices since positions changed _mTaskIndices.Clear(); } else // displacedtask == task, no need to move { } } } } /// /// Get the T tree /// public IEnumerable Tasks { get { var stack = new Stack(1024); var rstack = new Stack(30); foreach (var task in _mRootTasks) { stack.Push(task); while (stack.Count > 0) { var visited = stack.Pop(); yield return visited; foreach (var member in _mTaskGroups[visited]) rstack.Push(member); while (rstack.Count > 0) stack.Push(rstack.Pop()); } } } } /// /// Enumerate through all the children and grandchildren of the specified group /// public IEnumerable AncestorsOf(T task) { T parent = ParentOf(task); while (parent != null) { yield return parent; parent = ParentOf(parent); } } /// /// Enumerate through all the children and grandchildren of the specified group /// /// /// public IEnumerable DecendantsOf(T task) { if (_mRegister.Contains(task)) { Stack stack = new Stack(20); Stack rstack = new Stack(10); foreach (var child in _mTaskGroups[task]) { stack.Push(child); while (stack.Count > 0) { var visitedchild = stack.Pop(); yield return visitedchild; // push the grandchild rstack.Clear(); foreach (var grandchild in _mTaskGroups[visitedchild]) rstack.Push(grandchild); // put in the right visiting order while (rstack.Count > 0) stack.Push(rstack.Pop()); } } } } /// /// Enumerate through all the direct children of the specified group /// /// /// public IEnumerable ChildrenOf(T group) { if (group == null) yield break; List list; if (_mTaskGroups.TryGetValue(group, out list)) { var iter = list.GetEnumerator(); while (iter.MoveNext()) yield return iter.Current; } } /// /// Enumerate through all the direct precedents and indirect precedents of the specified task /// /// /// public IEnumerable PrecedentsOf(T task) { if (_mRegister.Contains(task)) { var stack = new Stack(20); foreach (var p in DirectPrecedentsOf(task)) { stack.Push(p); while (stack.Count > 0) { var visited = stack.Pop(); yield return visited; foreach (var grandp in DirectPrecedentsOf(visited)) stack.Push(grandp); } } } } /// /// Enumerate through all the direct dependants and indirect dependants of the specified task /// /// /// public IEnumerable DependantsOf(T task) { if (!_mDependents.ContainsKey(task)) yield break; var stack = new Stack(20); foreach (var d in _mDependents[task]) { stack.Push(d); while (stack.Count > 0) { var visited = stack.Pop(); yield return visited; foreach (var grandd in _mDependents[visited]) stack.Push(grandd); } } } /// /// Enumerate through all the direct precedents of the specified task /// /// /// public IEnumerable DirectPrecedentsOf(T task) { return _mDependents.Where(x => x.Value.Contains(task)).Select(x => x.Key); } /// /// Enumerate through all the direct dependants of the specified task /// /// /// public IEnumerable DirectDependantsOf(T task) { if (task == null) yield break; HashSet list; if (_mDependents.TryGetValue(task, out list)) { var iter = list.GetEnumerator(); while (iter.MoveNext()) yield return iter.Current; } } /// /// Enumerate through all tasks that is a precedent, having dependants. /// public IEnumerable Precedents { get { return _mDependents.Where(x => _mDependents[x.Key].Count > 0).Select(x => x.Key); } } /// /// Enumerate list of critical paths in Project /// public IEnumerable> CriticalPaths { get { Dictionary> endtimelookp = new Dictionary>(1024); List list; var max_end = (float) (int.MinValue); foreach (var task in this.Tasks) { if (!endtimelookp.TryGetValue(task.End, out list)) endtimelookp[task.End] = new List(10); endtimelookp[task.End].Add(task); if (task.End > max_end) max_end = task.End; } if (max_end != int.MinValue) { foreach (var task in endtimelookp[max_end]) { yield return new T[] {task}.Concat(PrecedentsOf(task)); } } } } /// /// Get the parent group of the specified task /// /// /// public T ParentOf(T task) { if (_mParentOfChild.ContainsKey(task)) // _mRegister.Contains(task)) { return _mParentOfChild[task]; } else { return null; } } /// /// Get whether the specified task is a group /// /// /// public bool IsGroup(T task) { List list; if (_mTaskGroups.TryGetValue(task, out list)) return list.Count > 0; else return false; } /// /// Get whether the specified task is a member /// /// /// public bool IsMember(T task) { return this.ParentOf(task) != null; } /// /// Get whether the specified task has relations, either has dependants or has precedents connecting to it. /// /// /// public bool HasRelations(T task) { if (_mRegister.Contains(task) && _mDependents.ContainsKey(task)) { return _mDependents[task].Count > 0 || DirectPrecedentsOf(task).FirstOrDefault() != null; } else { return false; } } /// /// Set a relation between the precedent and dependant task /// /// /// public void Relate(T precedent, T dependant) { if (_mRegister.Contains(precedent) && _mRegister.Contains(dependant) ) { if (_mSplitTaskOfPart.ContainsKey(precedent)) precedent = _mSplitTaskOfPart[precedent]; if (_mSplitTaskOfPart.ContainsKey(dependant)) dependant = _mSplitTaskOfPart[dependant]; if (!precedent.Equals(dependant) && !this.DependantsOf(dependant).Contains(precedent) //&& !this.IsGroup(precedent) //&& !this.IsGroup(dependant) ) { _mDependents[precedent].Add(dependant); _RecalculateDependantsOf(precedent); _RecalculateAncestorsSchedule(); _RecalculateSlack(); } } } /// /// Unset the relation between the precedent and dependant task, if any. /// /// /// public void Unrelate(T precedent, T dependant) { if (_mRegister.Contains(precedent) && _mRegister.Contains(dependant)) { if (_mSplitTaskOfPart.ContainsKey(precedent)) precedent = _mSplitTaskOfPart[precedent]; if (_mSplitTaskOfPart.ContainsKey(dependant)) dependant = _mSplitTaskOfPart[dependant]; _mDependents[precedent].Remove(dependant); _RecalculateSlack(); } } /// /// Remove all dependant task from specified precedent task /// /// public void Unrelate(T precedent) { if (_mRegister.Contains(precedent)) { if (_mSplitTaskOfPart.ContainsKey(precedent)) precedent = _mSplitTaskOfPart[precedent]; _mDependents[precedent].Clear(); _RecalculateSlack(); } } /// /// 当前节点直接父级删除关系 /// /// public void UnrelateParent(T precedent) { if (_mRegister.Contains(precedent)) { var parentTask = DirectPrecedentsOf(precedent); if (parentTask != null && parentTask.Count() > 0) { _mDependents[parentTask.First()].Remove(precedent); } _RecalculateSlack(); } } /// /// Assign the specified resource to the specified task /// /// /// public void Assign(T task, R resource) { if (_mRegister.Contains(task) && !_mResources[task].Contains(resource)) _mResources[task].Add(resource); } /// /// Unassign the specified resource from the specfied task /// /// /// public void Unassign(T task, R resource) { _mResources[task].Remove(resource); } /// /// Unassign the all resources from the specfied task /// /// public void Unassign(T task) { if (_mRegister.Contains(task)) _mResources[task].Clear(); } /// /// Unassign the specified resource from all tasks that has this resource assigned /// /// public void Unassign(R resource) { foreach (var r in _mResources.Where(x => x.Value.Contains(resource))) r.Value.Remove(resource); } /// /// Enumerate through all the resources that has been assigned to some task. /// public IEnumerable Resources { get { return _mResources.SelectMany(x => x.Value).Distinct(); } } /// /// Enumerate through all the resources that has been assigned to the specified task. /// /// /// public IEnumerable ResourcesOf(T task) { if (task == null || !_mRegister.Contains(task)) yield break; HashSet list; if (_mResources.TryGetValue(task, out list)) { foreach (var item in list) yield return item; } } /// /// Enumerate through all the tasks that has the specified resource assigned to it. /// /// /// public IEnumerable TasksOf(R resource) { return _mResources.Where(x => x.Value.Contains(resource)).Select(x => x.Key); } /// /// Set the start value. Affects group start/end and dependants start time. /// public void SetStart(T task, float value) { if (_mRegister.Contains(task) && value != task.Start && !this.IsGroup(task)) { _SetStartHelper(task, value); _RecalculateAncestorsSchedule(); _RecalculateSlack(); } // Set start for a group task else if (_mRegister.Contains(task) && value != task.Start && this.IsGroup(task)) { _SetGroupStartHelper(task, value); _RecalculateAncestorsSchedule(); _RecalculateSlack(); } } /// /// Set the end time. Affects group end and dependants start time. /// public void SetEnd(T task, float value) { if (_mRegister.Contains(task) && value != task.End && !this.IsGroup(task)) { this._SetEndHelper(task, value); _RecalculateAncestorsSchedule(); _RecalculateSlack(); } } public void SetDuration(T task, float duration) { this.SetEnd(task, task.Start + duration); } /// /// Set the percentage complete of the specified task from 0.0f to 1.0f. /// No effect on group tasks as they will get the aggregated percentage complete of all child tasks /// /// /// public void SetComplete(T task, float complete) { if (_mRegister.Contains(task) && complete != task.Complete && !this.IsGroup(task) // not a group && !_mSplitTasks.ContainsKey(task) // not a split task ) { _SetCompleteHelper(task, complete); _RecalculateComplete(); } } public void SetID(T task, string value) { task.ID = value; } /// /// Task类型 /// /// /// public void SetType(T task, int value) { task.Type = value; } /// /// 变量代码 /// /// /// public void SetTagID(T task, string value) { task.TagID = value; } /// /// 变量值 /// /// /// public void SetTagValue(T task, string value) { task.TagValue = value; } /// /// 变量值 来源 1 DT 2 MisData 3 扫描枪 /// /// /// public void SetTagFrom(T task, int value) { task.TagFrom = value; } /// /// 变量值 来源 1 DT 2 MisData 3 扫描枪 /// /// /// public void SetOpName(T task, string value) { task.OpName = value; } /// /// 拷贝变量值 /// /// /// public void TagIDCopyFrom(T task, string value) { task.TagIDCopyFrom = value; } /// /// 模型代码 /// /// /// public void SetModelCode(T task, int value) { task.ModelCode = value; } /// /// 模型是否可见 /// /// /// public void SetVisible(T task, int value) { task.Visible = value; } /// /// 机器人脚本 /// /// /// public void ScriptText(T task, string value) { task.ScriptText = value; } /// /// Set whether to collapse the specified group task. No effect on regular tasks. /// /// /// public void SetCollapse(T task, bool collasped) { if (_mRegister.Contains(task) && this.IsGroup(task)) { task.IsCollapsed = collasped; } } /// /// Split the specified task into consecutive parts part1 and part2. /// /// The regular task to split which has duration of at least 2 to make two parts of 1 time unit duration each. /// New Task part (1) of the split task, with the start time of the original task and the specified duration value. /// New Task part (2) of the split task, starting 1 time unit after part (1) ends and having the remaining of the duration of the origina task. /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary. public void Split(T task, T part1, T part2, float duration) { if (task != null && part1 != null && part2 != null && !part1.Equals(part2) // parts cannot be the same && _mRegister.Contains(task) // task must be registered && !_mSplitTasks.ContainsKey(task) // task must not already be a split task && !_mSplitTaskOfPart.ContainsKey(task) // task must not be a task part && _mTaskGroups[task].Count == 0 // task cannot be a group && !_mRegister.Contains(part1) // part1 and part2 must have never existed && !_mRegister.Contains(part2) ) { _mRegister.Add(part1); // register part1 _mResources[part1] = new HashSet(); // create container for holding resource // add part1 to split task task.Complete = 0.0f; // reset the complete status var parts = _mSplitTasks[task] = new List(2); parts.Add(part1); _mSplitTaskOfPart[part1] = task; // make a reverse lookup // allign the schedule if (duration >= task.Duration) duration--; part1.Start = task.Start; part1.End = task.End; part1.Duration = task.Duration; // split part1 to give part2 this.Split(part1, part2, duration); } } /// /// Split the specified part and obtain another part from it. /// /// The task part to split which has duration of at least 2 to make two parts of 1 time unit duration each. Its duration will be set to the specified duration value. /// New Task part of the original part, starting 1 time unit after it ends and having the remaining of the duration of the original part. /// The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary. public void Split(T part, T other, float duration) { if (part != null && other != null && _mSplitTaskOfPart.ContainsKey(part) // part must be an existing part && !_mRegister.Contains(other) // other must not have existed ) { _mRegister.Add(other); // register other part _mResources[other] = new HashSet(); // create container for holding resource var split = _mSplitTaskOfPart[part]; // get the split task var parts = _mSplitTasks[split]; // get the list of ordered parts parts.Insert(parts.IndexOf(part) + 1, other); // insert the other part after the existing part _mSplitTaskOfPart[other] = split; // set the reverse lookup if (part.Duration < 2) part.Duration = 2; // increase duration to allow for split if (duration < 1) duration = 1; // limit the duration point within the split task duration else if (duration >= part.Duration) duration = part.Duration - 1; // the real split var one_duration = duration; var two_duration = part.Duration - duration; part.Duration = one_duration; part.End = part.Start + one_duration; other.Duration = two_duration; other.Start = part.End + 1; other.End = other.Start + two_duration; _PackPartsForward(parts); split.Start = parts.First().Start; // recalculate the split task split.End = parts.Last().End; split.Duration = split.End - split.Start; _RecalculateDependantsOf(split); _RecalculateAncestorsSchedule(); } } /// /// Join part1 and part2 in a split task into a single part represented by part1, and part2 will be deleted from the ProjectManager. /// The resulting part will have a duration total of the two parts. /// Part1 and part2 must be actual parts and must be consecutive parts in the split task. /// If the join results in only one part remaining, the all parts will be deleted and the split task will promote to a regular task /// Schedule of other parts will not be affected. /// TODO: Join option: EarlyStartLateEnd, EarlyStartEarlyEnd, LateStartLateEnd /// /// The part to keep in the ProjectManager after the join completes successfully. /// The part to join into part1 and be deleted afterwards from the ProjectManager. public void Join(T part1, T part2) { if (part1 != null && part2 != null && _mSplitTaskOfPart.ContainsKey(part1) // part1 and part2 must already be existing parts && _mSplitTaskOfPart.ContainsKey(part2) && _mSplitTaskOfPart[part1] == _mSplitTaskOfPart[part2] // part1 and part2 must be of the same split task ) { var split = _mSplitTaskOfPart[part1]; var parts = _mSplitTasks[split]; if (parts.Count > 2) { // Aggregate part2 into part1, and determine join type float min; bool join_backwards; if (part1.Start < part2.Start) { min = part1.Start; join_backwards = true; } else { min = part2.Start; join_backwards = false; } float duration = part1.Duration + part2.Duration; part1.Start = min; part1.Duration = duration; part1.End = min + duration; // aggregate resouces // TODO: Ask whether to aggregate resources? foreach (var r in this.ResourcesOf(part2)) this.Assign(part1, r); this.Unassign(part2); // remove all traces of part2 parts.Remove(part2); _mResources.Remove(part2); _mSplitTaskOfPart.Remove(part2); _mRegister.Remove(part2); // pack the remaining parts if (join_backwards) _PackPartsForward(parts); else _PackPartsBackwards(parts); // set the duration split.End = parts.Last().End; split.Duration = split.End - split.Start; split.Start = parts.First().Start; _RecalculateAncestorsSchedule(); } else { this.Merge(split); } } } /// /// Merge all the parts of the splitted task back into one task, having duration equal to sum of total duration of individual task parts, and aggregating the resources onto the resulting task. /// /// The split Task to merge public void Merge(T split) { if (split != null && _mSplitTasks.ContainsKey(split) // must be existing split task ) { float duration = 0; _mSplitTasks[split].ForEach(x => { // sum durations duration += x.Duration; // merge resources onto split task foreach (var r in _mResources[x]) this.Assign(split, r); // remove traces of all parts _mSplitTaskOfPart.Remove(x); _mRegister.Remove(x); _mResources.Remove(x); }); _mSplitTasks.Remove(split); // remove split as a split task // set the duration this.SetDuration(split, duration); } } /// /// Get the parts of the split task /// /// /// public IEnumerable PartsOf(T split) { if (split != null && _mSplitTasks.ContainsKey(split) // must be existing split task ) { return _mSplitTasks[split].Select(x => x); } else { return new T[0]; } } /// /// Get the split task that the specified part belogs to. /// /// /// public T SplitTaskOf(T part) { if (_mSplitTaskOfPart.ContainsKey(part)) return _mSplitTaskOfPart[part]; return null; } /// /// Get whether the specified task is a split task /// /// /// public bool IsSplit(T task) { return task != null && _mSplitTasks.ContainsKey(task); } /// /// Get whether the specified task is a part of a split task /// /// /// public bool IsPart(T task) { return task != null && _mSplitTaskOfPart.ContainsKey(task); } /// /// Leave the parent group if task is a member, but remain registered in ProjectManager /// /// private void _LeaveParent(T task) { var parent = this.ParentOf(task); if (parent == null) _mRootTasks.Remove(task); else { _mTaskGroups[parent].Remove(task); _mParentOfChild[task] = null; } } private void _SetStartHelper(T task, float value) { if (task.Start != value) { if (_mSplitTaskOfPart.ContainsKey(task)) { // task part belonging to a split task needs special treatment _SetPartStartHelper(task, value); } else // regular task or a split task, which we will treat normally { // check out of bounds if (value < 0) value = 0; if (this.DirectPrecedentsOf(task).Any()) { var max_end = this.DirectPrecedentsOf(task).Max(x => x.End); //wangdequan 20200601 //if (value <= max_end) value = max_end + 1; } // save offset just in case we need to use for moving task parts var offset = value - task.Start; // cache value task.Duration = task.End - task.Start; task.Start = value; // affect self task.End = task.Start + task.Duration; // calculate dependants //_RecalculateDependantsOf(task); _RecalculateDependantsOfOffset(task, offset); // shift the task parts accordingly if task was a split task if (_mSplitTasks.ContainsKey(task)) { _mSplitTasks[task].ForEach(x => { x.Start += offset; x.End += offset; }); } } } } private void _RecalculateDependantsOfOffset(T precedent, float offset) { // affect decendants foreach (var dependant in this.DirectDependantsOf(precedent)) { //if (dependant.Start < precedent.End) if (IsGroup(dependant)) { this._SetGroupStartHelper(dependant, dependant.Start + offset); } else { this._SetStartHelper(dependant, dependant.Start + offset); } } } /// /// Set the start date for a group task. The relative dates between the tasks in the group will not be affected /// /// /// private void _SetGroupStartHelper(T group, float value) { if (_mRegister.Contains(group) && value != group.Start && this.IsGroup(group)) { bool earlier = value < group.Start; float offset = value - group.Start; var decendants = earlier ? MembersOf(group).OrderBy((t) => t.Start) : MembersOf(group).OrderByDescending((t) => t.Start); List children = new List(); // 找到没有依赖的,移动 foreach (T member in decendants) { var parentList = DirectPrecedentsOf(member); if (parentList != null && parentList.Count() == 0) { children.Add(member); } } foreach (T decendant in children) { // 调整作为 if (this.IsGroup(decendant)) continue; decendant.Start += offset; decendant.End += offset; if (this.IsSplit(decendant)) { var parts = _mSplitTasks[decendant]; foreach (T part in parts) { part.Start += offset; part.End += offset; } } //_RecalculateDependantsOf(decendant); _RecalculateDependantsOfOffset(decendant, offset); } _RecalculateAncestorsSchedule(); _RecalculateSlack(); } } /// /// Enumerate through all the children and grandchildren of the specified group /// /// /// public IEnumerable MembersOf(T group) { if (_mRegister.Contains(group)) { Stack stack = new Stack(20); Stack rstack = new Stack(10); foreach (var child in _mTaskGroups[group]) { stack.Push(child); while (stack.Count > 0) { var visitedchild = stack.Pop(); yield return visitedchild; // push the grandchild rstack.Clear(); foreach (var grandchild in _mTaskGroups[visitedchild]) rstack.Push(grandchild); // put in the right visiting order while (rstack.Count > 0) stack.Push(rstack.Pop()); } } } } private void _SetEndHelper(T task, float value) { if (task.End != value) { // 记录拖动前后时间 var delta = value - task.End; if (_mSplitTaskOfPart.ContainsKey(task)) { // task part belonging to a split task needs special treatment _SetPartEndHelper(task, value); } else // regular task or a split task, which we will treat normally { // check bounds bool isSplitTask = _mSplitTasks.ContainsKey(task); T last_part = null; if (isSplitTask) { last_part = _mSplitTasks[task].Last(); if (value <= last_part.Start) value = last_part.Start + 1; } if (value <= task.Start) value = task.Start + 1; // end cannot be less than start // assign end value task.End = value; task.Duration = task.End - task.Start; //_RecalculateDependantsOf(task); _RecalculateDependantsOfOffset(task, delta); if (isSplitTask) { last_part.End = value; last_part.Duration = last_part.End - last_part.Start; } } } } private void _SetPartStartHelper(T part, float value) { var split = _mSplitTaskOfPart[part]; var parts = _mSplitTasks[split]; // check bounds if (this.DirectPrecedentsOf(split).Any()) { var max_end = this.DirectPrecedentsOf(split).Max(x => x.End); if (value < max_end) value = max_end + 1; } if (value < 0) value = 0; // flag whether we need to pack parts forward or backwards bool backwards = value < part.Start; // assign start value, maintining duration and modifying end var duration = part.End - part.Start; part.Start = value; part.End = value + duration; // pack packs if (backwards) _PackPartsBackwards(parts); else _PackPartsForward(parts); // recalculate the split split.Start = parts.First().Start; // recalculate the split task split.End = parts.Last().End; split.Duration = split.End - split.Start; _RecalculateDependantsOf(split); } private void _SetPartEndHelper(T part, float value) { var split = _mSplitTaskOfPart[part]; var parts = _mSplitTasks[split]; // check for bounds if (value <= part.Start) value = part.Start + 1; // flag whether duration is increased or reduced bool increased = value > part.End; // set end value and duration part.End = value; part.Duration = part.End - part.Start; // pack parts if (increased) _PackPartsForward(parts); // recalculate the split split.Start = parts.First().Start; // recalculate the split task split.End = parts.Last().End; split.Duration = split.End - split.Start; _RecalculateDependantsOf(split); } private void _PackPartsBackwards(List parts) { // pack backwards first before packing forward again for (int i = parts.Count - 2; i > 0; i--) // Cannot pack beyond first part (i > 0) { var earlier = parts[i]; var later = parts[i + 1]; if (later.Start <= earlier.End) { earlier.End = later.Start - 1; earlier.Start = earlier.End - earlier.Duration; } } _PackPartsForward(parts); } private void _PackPartsForward(List parts) { for (int i = 1; i < parts.Count; i++) { var current = parts[i]; var previous = parts[i - 1]; if (previous.End >= current.Start) { current.Start = previous.End + 1; current.End = current.Start + current.Duration; } } } private void _SetCompleteHelper(T task, float value) { if (task.Complete != value) { if (value > 1) value = 1; else if (value < 0) value = 0; task.Complete = value; if (_mSplitTaskOfPart.ContainsKey(task)) { var split = _mSplitTaskOfPart[task]; var parts = _mSplitTasks[split]; float complete = 0; float duration = 0; foreach (var part in parts) { complete += part.Complete * part.Duration; duration += part.Duration; } split.Complete = complete / duration; } } } private void _RecalculateComplete() { Stack groups = new Stack(); foreach (var task in _mRootTasks.Where(x => this.IsGroup(x))) { _RecalculateCompletedHelper(task); } } private float _RecalculateCompletedHelper(T groupOrSplit) { float t_complete = 0; float t_duration = 0; if (_mSplitTasks.ContainsKey(groupOrSplit)) { foreach (var part in _mSplitTasks[groupOrSplit]) { t_complete += part.Complete * part.Duration; t_duration += part.Duration; } } else { foreach (var member in this.ChildrenOf(groupOrSplit)) { t_duration += member.Duration; if (this.IsGroup(member)) t_complete += _RecalculateCompletedHelper(member) * member.Duration; else t_complete += member.Complete * member.Duration; } } groupOrSplit.Complete = t_complete / t_duration; return groupOrSplit.Complete; } private void _RecalculateDependantsOf(T precedent) { // affect decendants foreach (var dependant in this.DirectDependantsOf(precedent)) { if (dependant.Start <= precedent.End) { //wangdequan 20200601 this._SetStartHelper(dependant, precedent.End); //this._SetStartHelper(dependant, precedent.End + 1); } } } private void _RecalculateAncestorsSchedule() { // affects parent group foreach (var group in _mRootTasks.Where(x => this.IsGroup(x))) { _RecalculateAncestorsScheduleHelper(group); } } private void _RecalculateAncestorsScheduleHelper(T group) { float t_complete = 0; float t_duration = 0; var start = (float) (int.MaxValue); var end = (float) (int.MinValue); foreach (var member in this.ChildrenOf(group)) { if (this.IsGroup(member)) _RecalculateAncestorsScheduleHelper(member); t_duration += member.Duration; t_complete += member.Complete * member.Duration; if (member.Start < start) start = member.Start; if (member.End > end) end = member.End; } this._SetStartHelper(group, start); this._SetEndHelper(group, end); this._SetCompleteHelper(group, t_complete / t_duration); } private void _RecalculateSlack() { var max_end = (float) (this.Tasks.Max(x => x.End)); foreach (var task in this.Tasks) { // affects slack for current task if (this.DirectDependantsOf(task).Any()) { // slack until the earliest dependant needs to start var min = this.DirectDependantsOf(task).Min(x => x.Start); task.Slack = min - task.End - 1; } else { // no dependants, so we have all the time until the last task ends task.Slack = max_end - task.End; } } } } }