1647 lines
57 KiB
C#
1647 lines
57 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
|
||
namespace Braincase.GanttChart
|
||
{
|
||
/// <summary>
|
||
/// Wrapper ProjectManager class
|
||
/// </summary>
|
||
[Serializable]
|
||
public class ProjectManager : ProjectManager<Task, object>
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// Concrete ProjectManager class for the IProjectManager interface
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <typeparam name="R"></typeparam>
|
||
[Serializable]
|
||
public class ProjectManager<T, R> : IProjectManager<T, R>
|
||
where T : Task
|
||
where R : class
|
||
{
|
||
HashSet<T> _mRegister = new HashSet<T>();
|
||
List<T> _mRootTasks = new List<T>();
|
||
Dictionary<T, List<T>> _mTaskGroups = new Dictionary<T, List<T>>();
|
||
Dictionary<T, HashSet<T>> _mDependents = new Dictionary<T, HashSet<T>>();
|
||
Dictionary<T, HashSet<R>> _mResources = new Dictionary<T, HashSet<R>>();
|
||
Dictionary<T, List<T>> _mSplitTasks = new Dictionary<T, List<T>>();
|
||
Dictionary<T, T> _mSplitTaskOfPart = new Dictionary<T, T>();
|
||
Dictionary<T, T> _mParentOfChild = new Dictionary<T, T>();
|
||
Dictionary<T, float> _mTaskIndices = new Dictionary<T, float>();
|
||
|
||
/// <summary>
|
||
/// Create a new Project
|
||
/// </summary>
|
||
public ProjectManager()
|
||
{
|
||
Now = 0;
|
||
Start = DateTime.Now;
|
||
TimeScale = GanttChart.TimeScale.Day;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get or set the period we are at now
|
||
/// </summary>
|
||
public int Now { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当前索引
|
||
/// </summary>
|
||
public int NowIndex { get; set; }
|
||
|
||
/// <summary>
|
||
/// 当前坐标位置
|
||
/// </summary>
|
||
public float NowIndexXf;
|
||
|
||
/// <summary>
|
||
/// 时间间隔 1,0.5 0.2 0.1,0.01 等
|
||
/// </summary>
|
||
public float TimeInterval = 0.1f;
|
||
|
||
/// <summary>
|
||
/// 工艺集合的最大时间,显示的坐标值。
|
||
/// </summary>
|
||
public float MaxCountLenth;
|
||
|
||
/// <summary>
|
||
/// 最大播放屏幕坐标长度,
|
||
/// </summary>
|
||
public float MaxPlayLenth;
|
||
|
||
/// <summary>
|
||
/// 总播放帧数
|
||
/// </summary>
|
||
public int AllCountFrame;
|
||
|
||
/// <summary>
|
||
/// 播放倍数
|
||
/// </summary>
|
||
public int PlayeTime = 1;
|
||
|
||
/// <summary>
|
||
/// 画播放标志
|
||
/// </summary>
|
||
public float DrawPlayLineMask = 5;
|
||
|
||
/// <summary>
|
||
/// 是否播放
|
||
/// </summary>
|
||
public bool IsPlaye = true;
|
||
|
||
/// <summary>
|
||
/// 是否播放
|
||
/// </summary>
|
||
public bool IsLastPlaye = false;
|
||
|
||
|
||
/// <summary>
|
||
/// Get or set the period we are at now wangdeqquan 20200531 当前开始序号
|
||
/// </summary>
|
||
public int StartIndex { get; set; }
|
||
|
||
//数据扩到 100倍,用整数代表小数
|
||
public float ValueScale = 1.5f;
|
||
|
||
/// <summary>
|
||
/// Get or set the starting date for this project
|
||
/// </summary>
|
||
public DateTime Start { get; set; }
|
||
|
||
/// <summary>
|
||
/// Get or set the time scale on this project. Each period on the task represents one unit of TimeScale.
|
||
/// </summary>
|
||
public TimeScale TimeScale { get; set; }
|
||
|
||
/// <summary>
|
||
/// Get the date after the specified period based on TimeScale
|
||
/// </summary>
|
||
/// <param name="period"></param>
|
||
/// <returns></returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Create a new T for this Project and add it to the T tree
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public void Add(T task)
|
||
{
|
||
if (!this._mRegister.Contains(task))
|
||
{
|
||
_mRegister.Add(task);
|
||
_mRootTasks.Add(task);
|
||
_mTaskGroups[task] = new List<T>();
|
||
_mDependents[task] = new HashSet<T>();
|
||
_mResources[task] = new HashSet<R>();
|
||
_mParentOfChild[task] = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Remove task from this Project
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Add the member T to the group T
|
||
/// </summary>
|
||
/// <param name="group"></param>
|
||
/// <param name="member"></param>
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Remove the member task from its group
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ungroup all member task under the specfied group task. The specified group task will become a normal task.
|
||
/// </summary>
|
||
/// <param name="group"></param>
|
||
public void Ungroup(T group)
|
||
{
|
||
List<T> 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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the zero-based index of the task in this Project
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Re-position the task by offset amount of places
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="offset"></param>
|
||
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
|
||
{
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the T tree
|
||
/// </summary>
|
||
public IEnumerable<T> Tasks
|
||
{
|
||
get
|
||
{
|
||
var stack = new Stack<T>(1024);
|
||
var rstack = new Stack<T>(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());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the children and grandchildren of the specified group
|
||
/// </summary>
|
||
public IEnumerable<T> AncestorsOf(T task)
|
||
{
|
||
T parent = ParentOf(task);
|
||
while (parent != null)
|
||
{
|
||
yield return parent;
|
||
parent = ParentOf(parent);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the children and grandchildren of the specified group
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> DecendantsOf(T task)
|
||
{
|
||
if (_mRegister.Contains(task))
|
||
{
|
||
Stack<T> stack = new Stack<T>(20);
|
||
Stack<T> rstack = new Stack<T>(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());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the direct children of the specified group
|
||
/// </summary>
|
||
/// <param name="group"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> ChildrenOf(T group)
|
||
{
|
||
if (group == null) yield break;
|
||
|
||
List<T> list;
|
||
if (_mTaskGroups.TryGetValue(group, out list))
|
||
{
|
||
var iter = list.GetEnumerator();
|
||
while (iter.MoveNext()) yield return iter.Current;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the direct precedents and indirect precedents of the specified task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> PrecedentsOf(T task)
|
||
{
|
||
if (_mRegister.Contains(task))
|
||
{
|
||
var stack = new Stack<T>(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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the direct dependants and indirect dependants of the specified task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> DependantsOf(T task)
|
||
{
|
||
if (!_mDependents.ContainsKey(task)) yield break;
|
||
|
||
var stack = new Stack<T>(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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the direct precedents of the specified task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> DirectPrecedentsOf(T task)
|
||
{
|
||
return _mDependents.Where(x => x.Value.Contains(task)).Select(x => x.Key);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the direct dependants of the specified task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> DirectDependantsOf(T task)
|
||
{
|
||
if (task == null) yield break;
|
||
|
||
HashSet<T> list;
|
||
if (_mDependents.TryGetValue(task, out list))
|
||
{
|
||
var iter = list.GetEnumerator();
|
||
while (iter.MoveNext()) yield return iter.Current;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all tasks that is a precedent, having dependants.
|
||
/// </summary>
|
||
public IEnumerable<T> Precedents
|
||
{
|
||
get { return _mDependents.Where(x => _mDependents[x.Key].Count > 0).Select(x => x.Key); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate list of critical paths in Project
|
||
/// </summary>
|
||
public IEnumerable<IEnumerable<T>> CriticalPaths
|
||
{
|
||
get
|
||
{
|
||
Dictionary<float, List<T>> endtimelookp = new Dictionary<float, List<T>>(1024);
|
||
List<T> 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<T>(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));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the parent group of the specified task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public T ParentOf(T task)
|
||
{
|
||
if (_mParentOfChild.ContainsKey(task)) // _mRegister.Contains(task))
|
||
{
|
||
return _mParentOfChild[task];
|
||
}
|
||
else
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get whether the specified task is a group
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public bool IsGroup(T task)
|
||
{
|
||
List<T> list;
|
||
if (_mTaskGroups.TryGetValue(task, out list))
|
||
return list.Count > 0;
|
||
else
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get whether the specified task is a member
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public bool IsMember(T task)
|
||
{
|
||
return this.ParentOf(task) != null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get whether the specified task has relations, either has dependants or has precedents connecting to it.
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public bool HasRelations(T task)
|
||
{
|
||
if (_mRegister.Contains(task) && _mDependents.ContainsKey(task))
|
||
{
|
||
return _mDependents[task].Count > 0 || DirectPrecedentsOf(task).FirstOrDefault() != null;
|
||
}
|
||
else
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Set a relation between the precedent and dependant task
|
||
/// </summary>
|
||
/// <param name="precedent"></param>
|
||
/// <param name="dependant"></param>
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unset the relation between the precedent and dependant task, if any.
|
||
/// </summary>
|
||
/// <param name="precedent"></param>
|
||
/// <param name="dependant"></param>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Remove all dependant task from specified precedent task
|
||
/// </summary>
|
||
/// <param name="precedent"></param>
|
||
public void Unrelate(T precedent)
|
||
{
|
||
if (_mRegister.Contains(precedent))
|
||
{
|
||
if (_mSplitTaskOfPart.ContainsKey(precedent))
|
||
precedent = _mSplitTaskOfPart[precedent];
|
||
|
||
_mDependents[precedent].Clear();
|
||
|
||
_RecalculateSlack();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前节点直接父级删除关系
|
||
/// </summary>
|
||
/// <param name="precedent"></param>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Assign the specified resource to the specified task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="resource"></param>
|
||
public void Assign(T task, R resource)
|
||
{
|
||
if (_mRegister.Contains(task) && !_mResources[task].Contains(resource))
|
||
_mResources[task].Add(resource);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unassign the specified resource from the specfied task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="resource"></param>
|
||
public void Unassign(T task, R resource)
|
||
{
|
||
_mResources[task].Remove(resource);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unassign the all resources from the specfied task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
public void Unassign(T task)
|
||
{
|
||
if (_mRegister.Contains(task))
|
||
_mResources[task].Clear();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unassign the specified resource from all tasks that has this resource assigned
|
||
/// </summary>
|
||
/// <param name="resource"></param>
|
||
public void Unassign(R resource)
|
||
{
|
||
foreach (var r in _mResources.Where(x => x.Value.Contains(resource)))
|
||
r.Value.Remove(resource);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the resources that has been assigned to some task.
|
||
/// </summary>
|
||
public IEnumerable<R> Resources
|
||
{
|
||
get { return _mResources.SelectMany(x => x.Value).Distinct(); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the resources that has been assigned to the specified task.
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<R> ResourcesOf(T task)
|
||
{
|
||
if (task == null || !_mRegister.Contains(task))
|
||
yield break;
|
||
|
||
HashSet<R> list;
|
||
if (_mResources.TryGetValue(task, out list))
|
||
{
|
||
foreach (var item in list)
|
||
yield return item;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the tasks that has the specified resource assigned to it.
|
||
/// </summary>
|
||
/// <param name="resource"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> TasksOf(R resource)
|
||
{
|
||
return _mResources.Where(x => x.Value.Contains(resource)).Select(x => x.Key);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Set the start value. Affects group start/end and dependants start time.
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Set the end time. Affects group end and dependants start time.
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="complete"></param>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Task类型
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void SetType(T task, int value)
|
||
{
|
||
task.Type = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 变量代码
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void SetTagID(T task, string value)
|
||
{
|
||
task.TagID = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 变量值
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void SetTagValue(T task, string value)
|
||
{
|
||
task.TagValue = value;
|
||
}
|
||
/// <summary>
|
||
/// 变量值 来源 1 DT 2 MisData 3 扫描枪
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void SetTagFrom(T task, int value)
|
||
{
|
||
task.TagFrom = value;
|
||
}
|
||
/// <summary>
|
||
/// 变量值 来源 1 DT 2 MisData 3 扫描枪
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void SetOpName(T task, string value)
|
||
{
|
||
task.OpName = value;
|
||
}
|
||
/// <summary>
|
||
/// 拷贝变量值
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void TagIDCopyFrom(T task, string value)
|
||
{
|
||
task.TagIDCopyFrom = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 模型代码
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void SetModelCode(T task, int value)
|
||
{
|
||
task.ModelCode = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 模型是否可见
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void SetVisible(T task, int value)
|
||
{
|
||
task.Visible = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 机器人脚本
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="value"></param>
|
||
public void ScriptText(T task, string value)
|
||
{
|
||
task.ScriptText = value;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Set whether to collapse the specified group task. No effect on regular tasks.
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <param name="collasped"></param>
|
||
public void SetCollapse(T task, bool collasped)
|
||
{
|
||
if (_mRegister.Contains(task) && this.IsGroup(task))
|
||
{
|
||
task.IsCollapsed = collasped;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Split the specified task into consecutive parts part1 and part2.
|
||
/// </summary>
|
||
/// <param name="task">The regular task to split which has duration of at least 2 to make two parts of 1 time unit duration each.</param>
|
||
/// <param name="part1">New Task part (1) of the split task, with the start time of the original task and the specified duration value.</param>
|
||
/// <param name="part2">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.</param>
|
||
/// <param name="duration">The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.</param>
|
||
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<R>(); // create container for holding resource
|
||
|
||
// add part1 to split task
|
||
task.Complete = 0.0f; // reset the complete status
|
||
var parts = _mSplitTasks[task] = new List<T>(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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Split the specified part and obtain another part from it.
|
||
/// </summary>
|
||
/// <param name="part">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.</param>
|
||
/// <param name="other">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.</param>
|
||
/// <param name="duration">The duration of part (1) will be set to the specified duration value but will also be adjusted to approperiate value if necessary.</param>
|
||
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<R>(); // 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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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
|
||
/// </summary>
|
||
/// <param name="part1">The part to keep in the ProjectManager after the join completes successfully.</param>
|
||
/// <param name="part2">The part to join into part1 and be deleted afterwards from the ProjectManager.</param>
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
/// <param name="split">The split Task to merge</param>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the parts of the split task
|
||
/// </summary>
|
||
/// <param name="split"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> 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];
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get the split task that the specified part belogs to.
|
||
/// </summary>
|
||
/// <param name="part"></param>
|
||
/// <returns></returns>
|
||
public T SplitTaskOf(T part)
|
||
{
|
||
if (_mSplitTaskOfPart.ContainsKey(part))
|
||
return _mSplitTaskOfPart[part];
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get whether the specified task is a split task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public bool IsSplit(T task)
|
||
{
|
||
return task != null && _mSplitTasks.ContainsKey(task);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Get whether the specified task is a part of a split task
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
/// <returns></returns>
|
||
public bool IsPart(T task)
|
||
{
|
||
return task != null && _mSplitTaskOfPart.ContainsKey(task);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Leave the parent group if task is a member, but remain registered in ProjectManager
|
||
/// </summary>
|
||
/// <param name="task"></param>
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Set the start date for a group task. The relative dates between the tasks in the group will not be affected
|
||
/// </summary>
|
||
/// <param name="group"></param>
|
||
/// <param name="value"></param>
|
||
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<T> children = new List<T>();
|
||
// 找到没有依赖的,移动
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enumerate through all the children and grandchildren of the specified group
|
||
/// </summary>
|
||
/// <param name="group"></param>
|
||
/// <returns></returns>
|
||
public IEnumerable<T> MembersOf(T group)
|
||
{
|
||
if (_mRegister.Contains(group))
|
||
{
|
||
Stack<T> stack = new Stack<T>(20);
|
||
Stack<T> rstack = new Stack<T>(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<T> 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<T> 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<T> groups = new Stack<T>();
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |