1use std::{
2 hint::unreachable_unchecked,
3 num::NonZeroU32,
4 path::{Path, PathBuf},
5 str::FromStr,
6};
7
8use either::Either;
9use flams_math_archives::{
10 backend::AnyBackend,
11 formats::{BuildSpec, BuildTargetId, TaskDependency, TaskRef},
12};
13use flams_utils::{
14 triomphe::Arc,
15 vecmap::{VecMap, VecSet},
16};
17use ftml_ontology::utils::time::Eta;
18use ftml_uris::{ArchiveId, ArchiveUri, DocumentUri, Language, ModuleUri, UriPath, UriWithArchive};
19use parking_lot::RwLock;
20
21mod queue;
22pub mod queue_manager;
23pub use queue::QueueName;
24mod queueing;
25
26pub(crate) static BUILD_QUEUE_SPAN: std::sync::LazyLock<tracing::Span> = std::sync::LazyLock::new(
27 || tracing::info_span!(target:"build queue",parent:None,"Build Queue"),
28);
29
30#[cfg(all(test, feature = "tokio"))]
31mod tests;
32
33pub use queue::Queue;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
36#[repr(u8)]
37pub enum TaskState {
38 Running = 0,
39 Queued = 1,
40 Blocked = 2,
41 Done = 3,
42 Failed = 4,
43 None = 5,
44}
45#[derive(Debug)]
46pub struct AtomicTaskState(std::sync::atomic::AtomicU8);
47impl AtomicTaskState {
48 pub fn new(state: TaskState) -> Self {
49 Self(std::sync::atomic::AtomicU8::new(state as _))
50 }
51 pub fn set_if_is(&self, if_is: TaskState, state: TaskState) {
52 self.0.compare_exchange(
53 if_is as _,
54 state as _,
55 std::sync::atomic::Ordering::Release,
56 std::sync::atomic::Ordering::Acquire,
57 );
58 }
59 pub fn get(&self) -> TaskState {
60 let b = self.0.load(std::sync::atomic::Ordering::Acquire);
61 match b {
62 0 => TaskState::Running,
63 1 => TaskState::Queued,
64 2 => TaskState::Blocked,
65 3 => TaskState::Done,
66 4 => TaskState::Failed,
67 5 => TaskState::None,
68 _ => unsafe { unreachable_unchecked() },
70 }
71 }
72 pub fn set(&self, state: TaskState) {
73 self.0
74 .store(state as _, std::sync::atomic::Ordering::Release);
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum Dependency {
80 Physical {
81 task: TaskRef,
82 strict: bool,
83 },
84 Logical {
85 uri: ModuleUri,
86 strict: bool,
87 },
88 Resolved {
89 task: BuildTask,
90 step: BuildTargetId,
91 strict: bool,
92 },
93}
94impl From<TaskDependency> for Dependency {
95 fn from(value: TaskDependency) -> Self {
96 match value {
97 TaskDependency::Logical { uri, strict } => Self::Logical { uri, strict },
98 TaskDependency::Physical { task, strict } => Self::Physical { task, strict },
99 }
100 }
101}
102
103#[derive(Copy, Clone, Debug, PartialEq, Eq)]
104pub struct BuildTaskId(NonZeroU32);
105impl From<BuildTaskId> for u32 {
106 #[inline]
107 fn from(id: BuildTaskId) -> Self {
108 id.0.get()
109 }
110}
111
112#[derive(Debug, PartialEq, Eq)]
113struct BuildTaskI {
114 id: BuildTaskId,
115 uri: DocumentUri,
116 steps: Box<[BuildStep]>,
117 source: Either<PathBuf, String>,
118 rel_path: UriPath,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct BuildTask(Arc<BuildTaskI>);
123impl BuildTask {
124 #[inline]
125 pub fn new(
127 id: BuildTaskId,
128 archive: ArchiveUri,
129 steps: Box<[BuildStep]>,
130 source: Either<PathBuf, String>,
131 rel_path: UriPath,
132 ) -> eyre::Result<Self> {
133 let uri = DocumentUri::from_archive_relpath(archive, rel_path.as_ref())
134 .map_err(eyre::Report::new)?;
135 Ok(Self(Arc::new(BuildTaskI {
136 uri,
137 id,
138 steps,
139 source,
140 rel_path,
141 })))
142 }
143
144 #[must_use]
145 #[inline]
146 pub fn document_uri(&self) -> &DocumentUri {
147 &self.0.uri
148 }
149
150 #[must_use]
151 pub fn as_build_spec<'a>(&'a self, backend: &'a AnyBackend) -> BuildSpec<'a> {
152 BuildSpec {
153 uri: &self.0.uri,
154 source: self.source(),
155 backend,
156 rel_path: self.rel_path(),
157 }
158 }
159
160 #[must_use]
161 pub fn as_task_ref(&self, target: BuildTargetId) -> TaskRef {
162 TaskRef {
163 archive: self.0.uri.archive_id().clone(),
164 rel_path: self.0.rel_path.clone(),
165 target,
166 }
167 }
168
169 #[inline]
170 #[must_use]
171 pub fn source(&self) -> Either<&Path, &str> {
172 match &self.0.source {
173 Either::Left(p) => Either::Left(p),
174 Either::Right(s) => Either::Right(s),
175 }
176 }
177
178 #[inline]
179 #[must_use]
180 pub fn archive(&self) -> &ArchiveUri {
181 self.0.uri.archive_uri()
182 }
183
184 #[inline]
185 #[must_use]
186 pub fn rel_path(&self) -> &UriPath {
187 &self.0.rel_path
188 }
189
190 #[inline]
191 #[must_use]
192 pub fn steps(&self) -> &[BuildStep] {
193 &self.0.steps
194 }
195
196 #[inline]
197 #[must_use]
198 pub fn get_step(&self, target: BuildTargetId) -> Option<&BuildStep> {
199 self.0.steps.iter().find(|s| s.0.target == target)
200 }
201
202 #[must_use]
203 #[allow(clippy::cast_possible_truncation)]
204 pub fn as_message(&self) -> QueueEntry {
205 QueueEntry {
211 id: self.0.id,
212 archive: self.0.uri.archive_id().clone(),
213 rel_path: self.0.rel_path.clone(),
214 steps: self
215 .steps()
216 .iter()
217 .map(|s| (s.0.target, s.0.state.get()))
218 .collect(),
219 }
220 }
221}
222
223#[derive(Debug)]
224struct BuildStepI {
225 target: BuildTargetId,
227 state: AtomicTaskState,
228 requires: RwLock<VecSet<Dependency>>,
230 dependents: RwLock<Vec<(BuildTaskId, BuildTargetId)>>,
231}
232impl PartialEq for BuildStepI {
233 fn eq(&self, other: &Self) -> bool {
234 self.target == other.target
235 }
236}
237impl Eq for BuildStepI {}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct BuildStep(Arc<BuildStepI>);
241impl BuildStep {
242 pub fn add_dependency(&self, dep: Dependency) {
243 self.0.requires.write().insert(dep);
244 }
245 }
252#[derive(Debug, Clone)]
307pub struct QueueEntry {
308 pub id: BuildTaskId,
309 pub archive: ArchiveId,
310 pub rel_path: UriPath,
311 pub steps: VecMap<BuildTargetId, TaskState>,
312}
313
314#[derive(Debug, Clone)]
315pub enum QueueMessage {
316 Idle(Vec<QueueEntry>),
317 Started {
318 running: Vec<QueueEntry>,
319 queue: Vec<QueueEntry>,
320 blocked: Vec<QueueEntry>,
321 failed: Vec<QueueEntry>,
322 done: Vec<QueueEntry>,
323 },
324 Finished {
325 failed: Vec<QueueEntry>,
326 done: Vec<QueueEntry>,
327 },
328 TaskStarted {
329 id: BuildTaskId,
330 target: BuildTargetId,
331 },
332 TaskSuccess {
333 id: BuildTaskId,
334 target: BuildTargetId,
335 eta: Eta,
336 },
337 TaskFailed {
338 id: BuildTaskId,
339 target: BuildTargetId,
340 eta: Eta,
341 },
342 TaskBlocked {
343 id: BuildTaskId,
344 target: BuildTargetId,
345 eta: Eta,
346 },
347}