Repository navigation
Expand file tree
/
Copy pathtask_graph.rs
More file actions
105 lines (97 loc) · 3.53 KB
/
Copy pathtask_graph.rs
File metadata and controls
105 lines (97 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! 纯领域任务图校验;不运行工具或获取锁。
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use crate::TaskNode;
/// 非法任务图原因;任何错误都必须在执行前发现。
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TaskGraphError {
/// 空或含控制字符的任务 ID。
InvalidTaskId,
/// 同一任务 ID 重复定义。
DuplicateTaskId(String),
/// 依赖不存在、自依赖或重复。
InvalidDependency(String),
/// 互斥资源名为空或重复。
InvalidResource(String),
/// 任务之间存在依赖环。
DependencyCycle,
}
/// 已校验的任务 DAG;按任务 ID 固定遍历顺序。
#[derive(Clone, Debug)]
pub struct TaskGraph {
tasks: BTreeMap<String, TaskNode>,
}
impl TaskGraph {
/// 校验任务 ID、依赖、资源和无环性;失败时不产生可运行图。
pub fn new(nodes: Vec<TaskNode>) -> Result<Self, TaskGraphError> {
let mut tasks = BTreeMap::new();
for node in nodes {
if node.id.trim().is_empty() || node.id.chars().any(char::is_control) {
return Err(TaskGraphError::InvalidTaskId);
}
let id = node.id.clone();
if tasks.insert(id.clone(), node).is_some() {
return Err(TaskGraphError::DuplicateTaskId(id));
}
}
let mut remaining = BTreeMap::new();
let mut children = BTreeMap::<String, Vec<String>>::new();
for task in tasks.values() {
let mut dependencies = BTreeSet::new();
for dependency in &task.dependencies {
if dependency == &task.id
|| !tasks.contains_key(dependency)
|| !dependencies.insert(dependency)
{
return Err(TaskGraphError::InvalidDependency(task.id.clone()));
}
children
.entry(dependency.clone())
.or_default()
.push(task.id.clone());
}
let mut resources = BTreeSet::new();
if task
.resources
.iter()
.any(|resource| resource.trim().is_empty() || !resources.insert(resource))
{
return Err(TaskGraphError::InvalidResource(task.id.clone()));
}
remaining.insert(task.id.clone(), dependencies.len());
}
let mut ready: VecDeque<_> = remaining
.iter()
.filter(|(_, count)| **count == 0)
.map(|(id, _)| id.clone())
.collect();
let mut visited = 0;
while let Some(id) = ready.pop_front() {
visited += 1;
for child in children.get(&id).into_iter().flatten() {
let count = remaining.get_mut(child).expect("已校验的依赖子任务");
*count -= 1;
if *count == 0 {
ready.push_back(child.clone());
}
}
}
if visited != tasks.len() {
return Err(TaskGraphError::DependencyCycle);
}
Ok(Self { tasks })
}
/// 按固定 ID 顺序遍历已校验节点。
pub fn tasks(&self) -> impl Iterator<Item = &TaskNode> {
self.tasks.values()
}
/// 返回图中的任务数量。
#[must_use]
pub fn len(&self) -> usize {
self.tasks.len()
}
/// 判断任务图是否为空;空图不代表质量检查已完成。
#[must_use]
pub fn is_empty(&self) -> bool {
self.tasks.is_empty()
}
}