Skip to main content

flams_system/building/
mod.rs

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            // SAFETY: impossible b< construction
69            _ => 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    /// # Errors
126    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        /*let idx = self.steps().iter().enumerate().find(|s|
206            matches!(&*s.1.0.state.read(),TaskState::Running | TaskState::Queued | TaskState::Blocked | TaskState::Failed)
207        );
208        let idx = if let Some((idx,_)) = idx {(idx - 1) as u8} else {self.steps().len() as u8};
209        */
210        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    //task:std::sync::Weak<BuildTaskI>,
226    target: BuildTargetId,
227    state: AtomicTaskState,
228    //yields:RwLock<Vec<ModuleUri>>,
229    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    /*
246    #[must_use]
247    pub fn get_task(&self) -> BuildTask {
248        BuildTask(self.0.task.upgrade().unwrap_or_else(|| unreachable!()))
249    }
250    */
251}
252/*
253pub trait BuildArtifact: Any + 'static {
254    fn get_type_id() -> BuildArtifactTypeId
255    where
256        Self: Sized;
257    /// #### Errors
258    fn load(p: &Path) -> Result<Self, std::io::Error>
259    where
260        Self: Sized;
261    fn get_type(&self) -> BuildArtifactTypeId;
262    /// ### Errors
263    fn write(&self, path: &Path) -> Result<(), std::io::Error>;
264    fn as_any(&self) -> &dyn Any;
265}
266
267pub enum BuildResultArtifact {
268    File(BuildArtifactTypeId, PathBuf),
269    Data(Box<dyn BuildArtifact>),
270    None,
271}
272
273pub struct BuildResult {
274    pub log: Either<String, PathBuf>,
275    pub result: Result<BuildResultArtifact, Vec<Dependency>>,
276}
277impl BuildResult {
278    #[must_use]
279    #[inline]
280    pub const fn empty() -> Self {
281        Self {
282            log: Either::Left(String::new()),
283            result: Ok(BuildResultArtifact::None),
284        }
285    }
286    #[must_use]
287    #[inline]
288    pub const fn err() -> Self {
289        Self {
290            log: Either::Left(String::new()),
291            result: Err(Vec::new()),
292        }
293    }
294
295    #[inline]
296    pub fn with_err(s: String) -> Self {
297        Self {
298            log: Either::Left(s),
299            result: Err(Vec::new()),
300        }
301    }
302}
303
304*/
305
306#[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}