1use std::collections::{BTreeMap, BTreeSet};
24use std::time::{Duration, Instant};
25
26use crate::pass::{PassManager, Pipeline, RunReport, Target};
27use crate::summary::SummaryRegistry;
28use crate::{Entity, Resource, World};
29
30pub type Error = Box<dyn std::error::Error + Send + Sync>;
31pub type Result<T> = std::result::Result<T, Error>;
32
33pub type Report = Vec<(String, String)>;
35
36pub trait Reader {
38 fn scope_spec(&self) -> Option<&'static crate::read_scope::ReadScopeSpec> {
41 None
42 }
43 fn read_scope(&self) -> Option<crate::read_scope::ReadScope> {
46 None
47 }
48 fn read(&mut self) -> Result<(World, Report)>;
50 fn load(&mut self, world: &World, roots: &BTreeSet<Entity>) -> Result<(World, Report)>;
53 fn load_all(&mut self, world: World) -> Result<(World, Report)> {
57 let roots: BTreeSet<Entity> = world.entities().collect();
58 self.load(&world, &roots)
59 }
60 fn emits(&self) -> &'static [&'static str];
64 fn prototype_box(&self, world: &World, prototype: Entity) -> Option<[[f64; 3]; 2]>;
66 fn notices(&mut self) -> Vec<Notice> {
69 Vec::new()
70 }
71}
72
73fn emitted(name: &str, emits: &[&str]) -> bool {
75 emits.contains(&name.split('.').next().unwrap_or(""))
76}
77
78pub fn check_emits(w: &World, emits: &[&str], when: &str) -> Result<()> {
80 if let Some(name) = w.component_names().into_iter().find(|n| !emitted(n, emits)) {
81 return Err(format!("{when}: the reader put {name} in the world (it emits {emits:?})").into());
82 }
83 if let Some(name) = w.resource_names().into_iter().find(|n| !emitted(n, emits)) {
84 return Err(
85 format!("{when}: the reader put the resource {name} in the world (it emits {emits:?})").into(),
86 );
87 }
88 Ok(())
89}
90
91pub fn check_load_emits(selected: &World, part: &World, emits: &[&str]) -> Result<()> {
97 let before: BTreeSet<&str> = selected.resource_names().into_iter().collect();
98 if let Some(name) = part
99 .resource_names()
100 .into_iter()
101 .find(|n| !emitted(n, emits) && !before.contains(n))
102 {
103 return Err(format!(
104 "load: the reader put the resource {name} in the part, which the selection had not (it emits {emits:?})"
105 )
106 .into());
107 }
108 for name in part.component_names() {
109 if emitted(name, emits) {
110 continue;
111 }
112 let before: BTreeSet<Entity> = selected.entities_named(name).into_iter().collect();
113 if let Some(e) = part
114 .entities_named(name)
115 .into_iter()
116 .find(|e| !before.contains(e))
117 {
118 return Err(format!(
119 "load: the reader put {name} on {e:?}, which the selection had not (it emits {emits:?})"
120 )
121 .into());
122 }
123 }
124 Ok(())
125}
126
127fn asked(
131 roots: &BTreeSet<Entity>,
132 shared: &BTreeSet<Entity>,
133 context: &BTreeSet<Entity>,
134) -> Result<BTreeSet<Entity>> {
135 for (set, what) in [(roots, "one of its roots"), (context, "its context")] {
136 if let Some(e) = shared.intersection(set).next() {
137 return Err(format!("part source: a part shares {e:?}, which is also {what}").into());
138 }
139 }
140 Ok(roots.iter().chain(shared).chain(context).copied().collect())
141}
142
143#[derive(Clone, Debug, PartialEq)]
149pub struct Part {
150 pub roots: BTreeSet<Entity>,
151 pub shared: BTreeSet<Entity>,
156 pub context: BTreeSet<Entity>,
157 pub whole: bool,
160 pub area: Option<[f64; 4]>,
165}
166
167pub trait PartSource {
169 fn plan(&mut self, world: &World, boxes: &dyn Fn(Entity) -> Option<[[f64; 3]; 2]>) -> Result<()>;
172 fn next_part(&mut self) -> Result<Option<Part>>;
174 fn written(&mut self, _part: &World) -> Result<()> {
176 Ok(())
177 }
178 fn restart(&mut self) -> Result<()> {
181 Err("this part source gives its parts once: it cannot be gone over twice".into())
182 }
183}
184
185#[derive(Debug, Default)]
187pub struct WholeWorld {
188 planned: BTreeSet<Entity>,
189 given: bool,
190}
191
192impl PartSource for WholeWorld {
193 fn plan(&mut self, world: &World, _boxes: &dyn Fn(Entity) -> Option<[[f64; 3]; 2]>) -> Result<()> {
194 self.planned = world.entities().collect();
195 self.given = false;
196 Ok(())
197 }
198 fn next_part(&mut self) -> Result<Option<Part>> {
199 if self.given {
200 return Ok(None);
201 }
202 self.given = true;
203 Ok(Some(Part {
204 roots: self.planned.clone(),
205 shared: BTreeSet::new(),
206 context: BTreeSet::new(),
207 whole: true,
208 area: None,
209 }))
210 }
211 fn restart(&mut self) -> Result<()> {
212 self.given = false;
213 Ok(())
214 }
215}
216
217#[derive(Clone, Debug, Default, PartialEq)]
221pub struct PartitionPlan {
222 pub by: String,
224 pub groups: Vec<PartitionGroup>,
225}
226
227#[derive(Clone, Debug, Default, PartialEq)]
229pub struct PartitionGroup {
230 pub key: String,
232 pub roots: BTreeSet<Entity>,
233 pub context: BTreeSet<Entity>,
234 pub area: Option<[f64; 4]>,
238}
239
240#[derive(Clone, Debug, PartialEq)]
244pub struct PartScope {
245 pub roots: BTreeSet<Entity>,
246 pub shared: BTreeSet<Entity>,
248 pub context: BTreeSet<Entity>,
249 pub whole: bool,
251 pub area: Option<[f64; 4]>,
253 pub first_made: Option<u32>,
257 pub placed: BTreeMap<Entity, bool>,
263}
264
265#[derive(Clone, Debug, Default, PartialEq)]
267pub struct PartOutput {
268 pub keep: BTreeSet<Entity>,
270 pub placed: BTreeSet<Entity>,
273 pub from_context: BTreeSet<Entity>,
275}
276
277impl PartScope {
278 pub fn output(&self, w: &World) -> PartOutput {
284 let mut out = PartOutput::default();
285 for (&e, &own) in self.placed.iter().filter(|(e, _)| w.is_alive(**e)) {
286 if own {
287 out.placed.insert(e);
288 } else {
289 out.from_context.insert(e);
290 }
291 }
292 out.keep = w.closure(self.roots.iter().chain(&self.shared).chain(&out.placed).copied());
293 out
294 }
295
296 pub fn owned(&self, w: &World) -> BTreeSet<Entity> {
299 let own = self.placed.iter().filter(|(e, own)| **own && w.is_alive(**e));
300 let mut owned = w.closure(self.roots.iter().copied().chain(own.map(|(e, _)| *e)));
301 owned.retain(|e| !self.shared.contains(e));
302 owned
303 }
304
305 pub fn declare(
311 w: &mut World,
312 placed: &BTreeSet<Entity>,
313 placed_from: &BTreeMap<Entity, Entity>,
314 owned_before: &BTreeSet<Entity>,
315 ) {
316 let Some(scope) = w.resource::<PartScope>() else {
317 return;
318 };
319 if placed.is_empty() {
320 return;
321 }
322 let shared = scope.shared.clone();
323 let mut owned = scope.owned(w);
324 owned.extend(owned_before.difference(&shared));
325 let mut own: BTreeSet<Entity> = BTreeSet::new();
326 loop {
328 let found: Vec<Entity> = placed
329 .iter()
330 .filter(|e| !own.contains(e))
331 .filter(|e| placed_from.get(e).is_none_or(|s| owned.contains(s)))
332 .copied()
333 .collect();
334 if found.is_empty() {
335 break;
336 }
337 owned.extend(found.iter().copied());
340 owned.extend(w.closure(found.iter().copied()).difference(&shared));
341 own.extend(found);
342 }
343 let classified: Vec<(Entity, bool)> = placed.iter().map(|e| (*e, own.contains(e))).collect();
344 if let Some(scope) = w.resource_mut::<PartScope>() {
345 scope.placed.extend(classified);
346 }
347 }
348}
349
350#[derive(Debug)]
352pub struct LoadedPart {
353 pub world: World,
354 pub load: Report,
356 loaded: BTreeSet<Entity>,
358}
359
360#[derive(Debug)]
362pub struct PreparedPart {
363 pub world: World,
365 pub report: PartReport,
369}
370
371fn load_checked(
375 reader: &mut dyn Reader,
376 world: &World,
377 part: &Part,
378 manager: &PassManager,
379 when: &str,
380 notices: &mut Vec<Notice>,
381) -> Result<(World, Report)> {
382 let asked = asked(&part.roots, &part.shared, &part.context)?;
383 let loaded = reader.load(world, &asked);
384 notices.extend(reader.notices());
385 let (loaded, report) = loaded?;
386 check_load_emits(world, &loaded, reader.emits())?;
387 manager.verify(&loaded, when)?;
389 Ok((loaded, report))
390}
391
392fn scoped(mut loaded: World, part: Part, load: Report, hand_on: impl FnOnce(&mut World)) -> LoadedPart {
395 let entities: BTreeSet<Entity> = loaded.entities().collect();
396 hand_on(&mut loaded);
397 let first_made = Some(loaded.next_index());
398 let Part {
399 roots,
400 shared,
401 context,
402 whole,
403 area,
404 } = part;
405 loaded.insert_resource(PartScope {
406 roots,
407 shared,
408 context,
409 whole,
410 area,
411 first_made,
412 placed: BTreeMap::new(),
413 });
414 LoadedPart {
415 world: loaded,
416 load,
417 loaded: entities,
418 }
419}
420
421pub fn load_part(
426 reader: &mut dyn Reader,
427 world: &World,
428 part: Part,
429 manager: &PassManager,
430 notices: &mut Vec<Notice>,
431) -> Result<LoadedPart> {
432 let (loaded, load) = load_checked(reader, world, &part, manager, "load", notices)?;
433 Ok(scoped(loaded, part, load, |_| {}))
434}
435
436pub fn prepare_loaded(
442 loaded: LoadedPart,
443 manager: &PassManager,
444 passes: &Pipeline,
445 explicit: &mut RunReport,
446 kept: &mut BTreeMap<String, u64>,
447) -> Result<World> {
448 let LoadedPart {
449 world: mut part,
450 loaded,
451 ..
452 } = loaded;
453 manager.run_part(&mut part, passes, explicit)?;
454 let scope = part
455 .remove_resource::<PartScope>()
456 .ok_or("the part's scope was taken out by its passes")?;
457 let PartOutput {
458 keep,
459 placed,
460 from_context,
461 } = scope.output(&part);
462 let names: Vec<String> = part.component_names().into_iter().map(str::to_owned).collect();
463 let kinds = |e: Entity| {
464 names
465 .iter()
466 .filter(|n| part.entity_has_named(e, n))
467 .map(String::as_str)
468 .collect::<Vec<_>>()
469 .join(" + ")
470 };
471 for e in part.entities().filter(|e| !loaded.contains(e)) {
472 let what = if placed.contains(&e) {
473 "entities the passes placed"
474 } else if from_context.contains(&e) {
475 "entities placed from context, left to the part that owns their source"
476 } else if keep.contains(&e) {
477 continue;
478 } else {
479 "made entities nothing keeps, dropped"
480 };
481 *kept.entry(format!("{what} ({})", kinds(e))).or_default() += 1;
482 }
483 part.retain(&keep);
484 manager.verify(&part, "the passes")?;
485 Ok(part)
486}
487
488pub fn prepare_part(
491 reader: &mut dyn Reader,
492 world: &World,
493 part: Part,
494 manager: &PassManager,
495 passes: &Pipeline,
496 notices: &mut Vec<Notice>,
497) -> Result<PreparedPart> {
498 let mut report = PartReport {
499 roots: part.roots.clone(),
500 shared: part.shared.clone(),
501 context: part.context.clone(),
502 ..PartReport::default()
503 };
504 let loaded = load_part(reader, world, part, manager, notices)?;
505 report.load = loaded.load.clone();
506 let world = prepare_loaded(loaded, manager, passes, &mut report.explicit, &mut report.kept)?;
507 Ok(PreparedPart { world, report })
508}
509
510impl Resource for PartScope {
511 const NAME: &'static str = "core.part_scope";
512 fn references(&self, out: &mut Vec<Entity>) {
513 out.extend(
514 self.roots
515 .iter()
516 .chain(&self.shared)
517 .chain(&self.context)
518 .copied(),
519 );
520 }
521 fn retain_references(&mut self, keep: &dyn Fn(Entity) -> bool) {
522 self.roots.retain(|&e| keep(e));
523 self.shared.retain(|&e| keep(e));
524 self.context.retain(|&e| keep(e));
525 }
526}
527
528pub const OPERATIONAL_RESOURCES: &[&str] = &[
531 PartitionPlan::NAME,
532 PartScope::NAME,
533 <primase_foundations::parallel::Threads as Resource>::NAME,
534];
535
536impl Resource for PartitionPlan {
537 const NAME: &'static str = "core.partition_plan";
538 fn to_json(&self) -> std::result::Result<Option<serde_json::Value>, crate::JsonError> {
539 let groups: Vec<serde_json::Value> = self
540 .groups
541 .iter()
542 .map(|g| {
543 serde_json::json!({
544 "key": g.key,
545 "roots": g.roots.iter().map(|e| e.index()).collect::<Vec<_>>(),
546 "context": g.context.iter().map(|e| e.index()).collect::<Vec<_>>(),
547 "area": g.area,
548 })
549 })
550 .collect();
551 Ok(Some(serde_json::json!({ "by": self.by, "groups": groups })))
552 }
553 fn verify(&self, _w: &World) -> std::result::Result<(), String> {
555 let mut keys = BTreeSet::new();
556 match self.groups.iter().find(|g| !keys.insert(g.key.as_str())) {
557 Some(g) => Err(format!("partition group key {:?} twice", g.key)),
558 None => Ok(()),
559 }
560 }
561 fn references(&self, out: &mut Vec<Entity>) {
562 for g in &self.groups {
563 out.extend(g.roots.iter().chain(&g.context).copied());
564 }
565 }
566 fn retain_references(&mut self, keep: &dyn Fn(Entity) -> bool) {
567 for g in &mut self.groups {
568 g.roots.retain(|&e| keep(e));
569 g.context.retain(|&e| keep(e));
570 }
571 }
572}
573
574#[derive(Debug, Default)]
579pub struct PlannedParts {
580 planned: Vec<PartitionGroup>,
581 groups: std::vec::IntoIter<PartitionGroup>,
582 single: bool,
584}
585
586fn ground_box(t: &crate::Transform, b: [[f64; 3]; 2]) -> [f64; 4] {
588 let mut out = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
589 for k in 0..8 {
590 let c = [b[k & 1][0], b[(k >> 1) & 1][1], b[(k >> 2) & 1][2]];
591 let p: [f64; 3] = std::array::from_fn(|i| {
592 (0..3).map(|j| f64::from(t.cols[j][i]) * c[j]).sum::<f64>() + f64::from(t.cols[3][i])
593 });
594 let (east, north) = (p[0], -p[2]);
595 out = [
596 out[0].min(east),
597 out[1].min(north),
598 out[2].max(east),
599 out[3].max(north),
600 ];
601 }
602 out
603}
604
605impl PartSource for PlannedParts {
606 fn plan(&mut self, world: &World, boxes: &dyn Fn(Entity) -> Option<[[f64; 3]; 2]>) -> Result<()> {
607 let plan = world
608 .resource::<PartitionPlan>()
609 .ok_or("no core.partition_plan in the selected world: run a partition pass in the selection")?;
610 let placed: Vec<(Entity, [f64; 4])> = world
611 .iter::<crate::Instance>()
612 .filter_map(|(e, i)| {
613 let t = world.get::<crate::Transform>(e)?;
614 Some((e, ground_box(t, boxes(i.prototype)?)))
615 })
616 .collect();
617 let prototypes: BTreeSet<Entity> = world
619 .iter::<crate::Instance>()
620 .map(|(_, i)| i.prototype)
621 .collect();
622 let soups: Vec<(Entity, [f64; 4])> = world
623 .iter::<crate::Collision>()
624 .filter(|(e, c)| !prototypes.contains(e) && !c.positions.is_empty())
625 .map(|(e, c)| {
626 let t = world
627 .get::<crate::Transform>(e)
628 .copied()
629 .unwrap_or(crate::Transform::IDENTITY);
630 let mut b = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
631 for p in &c.positions {
632 let q = t.apply(*p);
633 let (east, north) = (f64::from(q[0]), -f64::from(q[2]));
634 b = [b[0].min(east), b[1].min(north), b[2].max(east), b[3].max(north)];
635 }
636 (e, b)
637 })
638 .collect();
639 let mut groups = plan.groups.clone();
640 for g in &mut groups {
641 let Some(a) = g.area else { continue };
642 for &(e, b) in placed.iter().chain(&soups) {
643 if !g.roots.contains(&e) && b[0] < a[2] && b[2] >= a[0] && b[1] < a[3] && b[3] >= a[1] {
644 g.context.insert(e);
645 }
646 }
647 }
648 self.single = groups.len() == 1;
649 self.planned = groups.clone();
650 self.groups = groups.into_iter();
651 Ok(())
652 }
653 fn restart(&mut self) -> Result<()> {
654 self.groups = self.planned.clone().into_iter();
655 Ok(())
656 }
657 fn next_part(&mut self) -> Result<Option<Part>> {
658 let single = self.single;
660 Ok(self.groups.next().map(|g| Part {
661 whole: single && g.context.is_empty(),
662 roots: g.roots,
663 shared: BTreeSet::new(),
664 context: g.context,
665 area: g.area,
666 }))
667 }
668}
669
670pub trait Writer {
674 fn target(&self) -> Target;
677 fn write(&mut self, part: &World) -> Result<()>;
678 fn notices(&mut self) -> Vec<Notice> {
681 Vec::new()
682 }
683 fn finish(self: Box<Self>) -> Result<Finished>;
685}
686
687#[derive(Clone, Debug, Default, PartialEq, Eq)]
689pub struct Finished {
690 pub report: Report,
691 pub notices: Vec<Notice>,
693}
694
695impl From<Report> for Finished {
696 fn from(report: Report) -> Finished {
697 Finished {
698 report,
699 notices: Vec::new(),
700 }
701 }
702}
703
704#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
706pub enum NoticeKind {
707 Unsupported,
709 Approximation,
711 Selection,
713 FailureContext,
715 Diagnostic,
717}
718
719pub const NOTICE_EXAMPLES: usize = 8;
721
722#[derive(Clone, Debug, PartialEq, Eq)]
725pub struct Notice {
726 pub stage: String,
728 pub kind: NoticeKind,
729 pub reason: String,
730 pub count: u64,
731 pub examples: Vec<String>,
733 pub entities: Vec<Entity>,
738}
739
740impl Notice {
741 pub fn new(stage: impl Into<String>, kind: NoticeKind, reason: impl Into<String>, count: u64) -> Notice {
742 Notice {
743 stage: stage.into(),
744 kind,
745 reason: reason.into(),
746 count,
747 examples: Vec::new(),
748 entities: Vec::new(),
749 }
750 }
751
752 pub fn example(mut self, e: impl Into<String>) -> Notice {
754 if self.examples.len() < NOTICE_EXAMPLES {
755 self.examples.push(e.into());
756 }
757 self
758 }
759}
760
761impl std::fmt::Display for Notice {
762 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
764 let kind = match self.kind {
765 NoticeKind::Unsupported => "unsupported",
766 NoticeKind::Approximation => "approximation",
767 NoticeKind::Selection => "selection",
768 NoticeKind::FailureContext => "failure",
769 NoticeKind::Diagnostic => "diagnostic",
770 };
771 write!(f, "{kind} {}: {} x {}", self.stage, self.reason, self.count)?;
772 if !self.examples.is_empty() {
773 write!(f, " ({})", self.examples.join(", "))?;
774 }
775 Ok(())
776 }
777}
778
779#[derive(Debug, Default)]
781pub struct PartReport {
782 pub ordinal: usize,
784 pub roots: BTreeSet<Entity>,
786 pub shared: BTreeSet<Entity>,
788 pub context: BTreeSet<Entity>,
790 pub load: Report,
792 pub explicit: RunReport,
794 pub unsupported: Vec<(String, u64)>,
796 pub unsupported_resources: Vec<String>,
798 pub kept: BTreeMap<String, u64>,
801}
802
803#[derive(Clone, Debug, Default)]
806pub struct GroupReport {
807 pub ordinal: usize,
808 pub run: RunReport,
809 pub unsupported: Vec<(String, u64)>,
810 pub unsupported_resources: Vec<String>,
811 pub written: BTreeMap<String, u64>,
812}
813
814#[derive(Debug, Default)]
816pub struct ConvertReport {
817 pub read: Report,
819 pub select: RunReport,
821 pub loads: Report,
823 pub summary_loads: Report,
826 pub summarized: Vec<RunReport>,
827 pub decided: RunReport,
828 pub groups: Vec<GroupReport>,
831 pub finalized: RunReport,
832 pub gather_loads: Report,
835 pub gather_runs: Vec<RunReport>,
836 pub summary_bytes: usize,
840 pub merged_summary_bytes: usize,
841 pub parts: usize,
842 pub part_sizes: Vec<PartSize>,
844 pub counts: BTreeMap<String, u64>,
847 pub part_reports: Vec<PartReport>,
849 pub reported: Vec<Notice>,
851 pub written: Report,
853 pub stages: Vec<(String, Duration)>,
858}
859
860impl ConvertReport {
861 pub fn approximations(&self) -> BTreeMap<String, u64> {
863 let mut out = BTreeMap::new();
864 let runs = [&self.select, &self.decided, &self.finalized]
865 .into_iter()
866 .chain(self.groups.iter().map(|g| &g.run))
867 .chain(self.part_reports.iter().map(|p| &p.explicit));
868 for run in runs {
869 for step in &run.steps {
870 for (k, v) in &step.log.approximations {
871 *out.entry(format!("{}: {k}", step.pass)).or_default() += v;
872 }
873 }
874 }
875 for (traversal, runs) in self.preparing_traversals() {
876 for step in runs.iter().flat_map(|r| &r.steps) {
877 for (k, v) in &step.log.approximations {
878 *out.entry(format!("{} ({traversal}): {k}", step.pass))
879 .or_default() += v;
880 }
881 }
882 }
883 out
884 }
885
886 fn preparing_traversals(&self) -> [(&'static str, &Vec<RunReport>); 2] {
890 [
891 ("summary traversal", &self.summarized),
892 ("gather traversal", &self.gather_runs),
893 ]
894 }
895
896 pub fn notices(&self) -> Vec<Notice> {
900 let mut out = self.reported.clone();
901 let mut derived: BTreeMap<(String, NoticeKind, String), (u64, Vec<String>)> = BTreeMap::new();
902 let attributed: std::cell::RefCell<BTreeMap<(String, NoticeKind, String), BTreeSet<Entity>>> =
904 std::cell::RefCell::new(BTreeMap::new());
905 let mut add = |stage: &str, kind: NoticeKind, reason: &str, n: u64, part: Option<String>| {
906 let (count, examples) = derived
907 .entry((stage.to_owned(), kind, reason.to_owned()))
908 .or_default();
909 *count += n;
910 if let Some(at) = part
911 && examples.len() < NOTICE_EXAMPLES
912 {
913 examples.push(at);
914 }
915 };
916 let whole = [&self.select, &self.decided, &self.finalized]
917 .into_iter()
918 .map(|r| (r, None))
919 .chain(
920 self.groups
921 .iter()
922 .map(|g| (&g.run, Some(format!("group {}", g.ordinal)))),
923 );
924 for (run, at) in whole {
925 for step in &run.steps {
926 for (k, v) in &step.log.approximations {
927 add(&step.pass, NoticeKind::Approximation, k, *v, at.clone());
928 if at.is_none()
929 && let Some(on) = step.log.approximated_on.get(k)
930 {
931 attributed
932 .borrow_mut()
933 .entry((step.pass.clone(), NoticeKind::Approximation, k.clone()))
934 .or_default()
935 .extend(on.iter().copied());
936 }
937 }
938 for (k, v) in &step.log.omitted {
939 add(&step.pass, NoticeKind::Unsupported, k, *v, at.clone());
940 }
941 for ((m, why), v) in &step.log.drops {
942 add(
943 &step.pass,
944 NoticeKind::Selection,
945 &format!("{m}: {why}"),
946 *v,
947 at.clone(),
948 );
949 }
950 for g in &step.conservation {
951 add(&step.pass, NoticeKind::Diagnostic, &g.to_string(), 1, at.clone());
952 }
953 }
954 }
955 for (traversal, runs) in self.preparing_traversals() {
956 for (i, run) in runs.iter().enumerate() {
957 for step in &run.steps {
958 let stage = format!("{} ({traversal})", step.pass);
959 let at = || Some(format!("part {i}"));
960 for (k, v) in &step.log.approximations {
961 add(&stage, NoticeKind::Approximation, k, *v, at());
962 }
963 for (k, v) in &step.log.omitted {
964 add(&stage, NoticeKind::Unsupported, k, *v, at());
965 }
966 for ((m, why), v) in &step.log.drops {
967 add(&stage, NoticeKind::Selection, &format!("{m}: {why}"), *v, at());
968 }
969 for g in &step.conservation {
970 add(&stage, NoticeKind::Diagnostic, &g.to_string(), 1, at());
971 }
972 }
973 }
974 }
975 for g in &self.groups {
976 for (name, n) in &g.unsupported {
977 add(
978 "convert",
979 NoticeKind::Unsupported,
980 &format!("{name}: the writer doesn't write it"),
981 *n,
982 Some(format!("group {}", g.ordinal)),
983 );
984 }
985 for name in &g.unsupported_resources {
986 add(
987 "convert",
988 NoticeKind::Unsupported,
989 &format!("resource {name}: the writer doesn't write it"),
990 1,
991 Some(format!("group {}", g.ordinal)),
992 );
993 }
994 }
995 for p in &self.part_reports {
996 for step in &p.explicit.steps {
997 for (k, v) in &step.log.approximations {
998 add(
999 &step.pass,
1000 NoticeKind::Approximation,
1001 k,
1002 *v,
1003 Some(format!("part {}", p.ordinal)),
1004 );
1005 }
1006 for (k, v) in &step.log.omitted {
1007 add(
1008 &step.pass,
1009 NoticeKind::Unsupported,
1010 k,
1011 *v,
1012 Some(format!("part {}", p.ordinal)),
1013 );
1014 }
1015 for ((m, why), v) in &step.log.drops {
1016 add(
1017 &step.pass,
1018 NoticeKind::Selection,
1019 &format!("{m}: {why}"),
1020 *v,
1021 Some(format!("part {}", p.ordinal)),
1022 );
1023 }
1024 for g in &step.conservation {
1025 add(
1026 &step.pass,
1027 NoticeKind::Diagnostic,
1028 &g.to_string(),
1029 1,
1030 Some(format!("part {}", p.ordinal)),
1031 );
1032 }
1033 }
1034 for (k, v) in &p.kept {
1035 if k.starts_with("made entities nothing keeps") {
1036 add(
1037 "convert",
1038 NoticeKind::Selection,
1039 k,
1040 *v,
1041 Some(format!("part {}", p.ordinal)),
1042 );
1043 }
1044 }
1045 for (name, n) in &p.unsupported {
1046 add(
1047 "convert",
1048 NoticeKind::Unsupported,
1049 &format!("{name}: the writer doesn't write it"),
1050 *n,
1051 Some(format!("part {}", p.ordinal)),
1052 );
1053 }
1054 for name in &p.unsupported_resources {
1055 add(
1056 "convert",
1057 NoticeKind::Unsupported,
1058 &format!("resource {name}: the writer doesn't write it"),
1059 1,
1060 Some(format!("part {}", p.ordinal)),
1061 );
1062 }
1063 }
1064 out.extend(
1065 derived
1066 .into_iter()
1067 .map(|((stage, kind, reason), (count, examples))| Notice {
1068 entities: attributed
1069 .borrow()
1070 .get(&(stage.clone(), kind, reason.clone()))
1071 .map(|e| e.iter().copied().take(NOTICE_EXAMPLES).collect())
1072 .unwrap_or_default(),
1073 stage,
1074 kind,
1075 reason,
1076 count,
1077 examples,
1078 }),
1079 );
1080 out
1081 }
1082}
1083
1084#[derive(Debug)]
1088pub struct ConvertError {
1089 pub error: Error,
1090 pub during: String,
1092 pub partial: Box<ConvertReport>,
1093}
1094
1095impl std::fmt::Display for ConvertError {
1096 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1097 self.error.fmt(f)
1098 }
1099}
1100
1101impl std::error::Error for ConvertError {
1102 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1103 Some(self.error.as_ref())
1104 }
1105}
1106
1107#[derive(Debug)]
1109struct Stages {
1110 during: String,
1111 started: Instant,
1112 done: Vec<(String, Duration)>,
1113}
1114
1115impl Stages {
1116 fn new() -> Stages {
1117 Stages {
1118 during: String::new(),
1119 started: Instant::now(),
1120 done: Vec::new(),
1121 }
1122 }
1123
1124 fn enter(&mut self, stage: impl Into<String>) {
1126 self.close();
1127 self.during = stage.into();
1128 }
1129
1130 fn close(&mut self) {
1131 if !self.during.is_empty() {
1132 self.done.push((self.during.clone(), self.started.elapsed()));
1133 }
1134 self.started = Instant::now();
1135 }
1136}
1137
1138fn finish(mut report: ConvertReport, mut stages: Stages, ran: Result<()>) -> Result<ConvertReport> {
1141 stages.close();
1142 report.stages = std::mem::take(&mut stages.done);
1143 match ran {
1144 Ok(()) => Ok(report),
1145 Err(error) => {
1146 let during = stages.during;
1147 report.reported.push(Notice::new(
1148 "convert",
1149 NoticeKind::FailureContext,
1150 format!("failed during {during}: {error}"),
1151 1,
1152 ));
1153 Err(Box::new(ConvertError {
1154 error,
1155 during,
1156 partial: Box::new(report),
1157 }))
1158 }
1159 }
1160}
1161
1162#[derive(Clone, Debug, Default, PartialEq, Eq)]
1164pub struct PartSize {
1165 pub roots: usize,
1167 pub loaded: BTreeMap<String, u64>,
1169 pub written: BTreeMap<String, u64>,
1171}
1172
1173fn sizes(w: &World) -> BTreeMap<String, u64> {
1174 w.payload_bytes()
1175 .into_iter()
1176 .map(|(n, b)| (n.to_owned(), b))
1177 .collect()
1178}
1179
1180fn tally(counts: &mut BTreeMap<String, u64>, report: &RunReport) {
1182 for step in &report.steps {
1183 for (k, v) in &step.log.counts {
1184 *counts.entry(format!("{}: {k}", step.pass)).or_default() += v;
1185 }
1186 }
1187}
1188
1189pub fn convert_parts(
1193 reader: &mut dyn Reader,
1194 parts: &mut dyn PartSource,
1195 writer: Box<dyn Writer>,
1196 manager: &PassManager,
1197 select: &Pipeline,
1198 passes: &Pipeline,
1199) -> Result<ConvertReport> {
1200 let mut report = ConvertReport::default();
1201 let mut stages = Stages::new();
1202 let ran = run(
1203 reader,
1204 parts,
1205 writer,
1206 manager,
1207 select,
1208 None,
1209 passes,
1210 &mut report,
1211 &mut stages,
1212 );
1213 finish(report, stages, ran)
1214}
1215
1216#[derive(Debug)]
1218pub struct Summarized<'a> {
1219 pub registry: &'a SummaryRegistry,
1221 pub summarize: &'a Pipeline,
1223 pub decide: &'a Pipeline,
1225}
1226
1227#[allow(clippy::too_many_arguments)]
1233pub fn convert_parts_summarized(
1234 reader: &mut dyn Reader,
1235 parts: &mut dyn PartSource,
1236 writer: Box<dyn Writer>,
1237 manager: &PassManager,
1238 select: &Pipeline,
1239 summarized: &Summarized,
1240 passes: &Pipeline,
1241) -> Result<ConvertReport> {
1242 let mut report = ConvertReport::default();
1243 let mut stages = Stages::new();
1244 let ran = run(
1245 reader,
1246 parts,
1247 writer,
1248 manager,
1249 select,
1250 Some((summarized, None)),
1251 passes,
1252 &mut report,
1253 &mut stages,
1254 );
1255 finish(report, stages, ran)
1256}
1257
1258pub trait Spool {
1262 fn take(&mut self, part: &mut World) -> Result<()>;
1265 fn next(&mut self) -> Result<Option<World>>;
1268}
1269
1270pub struct Gathered<'a> {
1275 pub spool: &'a mut dyn Spool,
1276 pub gather: &'a Pipeline,
1277 pub group: &'a Pipeline,
1278 pub finalize: &'a Pipeline,
1279}
1280
1281impl std::fmt::Debug for Gathered<'_> {
1282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1283 f.debug_struct("Gathered")
1284 .field("gather", self.gather)
1285 .field("group", self.group)
1286 .field("finalize", self.finalize)
1287 .finish_non_exhaustive()
1288 }
1289}
1290
1291#[allow(clippy::too_many_arguments)]
1300pub fn convert_parts_gathered(
1301 reader: &mut dyn Reader,
1302 parts: &mut dyn PartSource,
1303 writer: Box<dyn Writer>,
1304 manager: &PassManager,
1305 select: &Pipeline,
1306 summarized: &Summarized,
1307 gathered: Gathered,
1308 passes: &Pipeline,
1309) -> Result<ConvertReport> {
1310 let mut report = ConvertReport::default();
1311 let mut stages = Stages::new();
1312 let mut gathered = gathered;
1313 let ran = run(
1314 reader,
1315 parts,
1316 writer,
1317 manager,
1318 select,
1319 Some((summarized, Some(&mut gathered))),
1320 passes,
1321 &mut report,
1322 &mut stages,
1323 );
1324 finish(report, stages, ran)
1325}
1326
1327type NotWritten = (Vec<(String, u64)>, Vec<String>);
1331
1332fn check_target(target: &Target, w: &World) -> Result<NotWritten> {
1333 let checked = target.check_prepared(w);
1334 if let Some((name, words, bad)) = checked.invalid.first() {
1335 return Err(format!(
1336 "{}: {} of {name} the writer can't write ({words}), the first {:?}",
1337 target.name,
1338 bad.len(),
1339 bad[0]
1340 )
1341 .into());
1342 }
1343 if let Some(name) = checked.invalid_resources.first() {
1344 return Err(format!(
1345 "{}: resource {name} refused ({})",
1346 target.name,
1347 target.rule_words(name).unwrap_or_default()
1348 )
1349 .into());
1350 }
1351 Ok((checked.unsupported, checked.unsupported_resources))
1352}
1353
1354pub fn convert(
1356 reader: &mut dyn Reader,
1357 writer: Box<dyn Writer>,
1358 manager: &PassManager,
1359 select: &Pipeline,
1360 passes: &Pipeline,
1361) -> Result<ConvertReport> {
1362 convert_parts(
1363 reader,
1364 &mut WholeWorld::default(),
1365 writer,
1366 manager,
1367 select,
1368 passes,
1369 )
1370}
1371
1372#[allow(clippy::too_many_arguments)]
1373fn run(
1374 reader: &mut dyn Reader,
1375 parts: &mut dyn PartSource,
1376 mut writer: Box<dyn Writer>,
1377 manager: &PassManager,
1378 select: &Pipeline,
1379 summarized: Option<(&Summarized, Option<&mut Gathered>)>,
1380 passes: &Pipeline,
1381 report: &mut ConvertReport,
1382 stages: &mut Stages,
1383) -> Result<()> {
1384 stages.enter("read");
1385 let read = reader.read();
1386 report.reported.extend(reader.notices());
1387 let (mut world, read) = read?;
1388 report.read = read;
1389 check_emits(&world, reader.emits(), "read")?;
1390 manager.verify(&world, "read")?;
1392 stages.enter("select");
1393 manager.run(&mut world, select, &mut report.select)?;
1394 let target = writer.target();
1395 stages.enter("plan");
1396 parts.plan(&world, &|p| reader.prototype_box(&world, p))?;
1397 let decided = match summarized {
1400 None => None,
1401 Some((s, gathered)) => {
1402 let mut kept = Vec::new();
1403 while let Some(Part {
1404 roots,
1405 shared,
1406 context,
1407 whole,
1408 area,
1409 }) = parts.next_part()?
1410 {
1411 let ordinal = kept.len();
1412 stages.enter(format!("summary {ordinal}: load"));
1413 let part = Part {
1414 roots,
1415 shared,
1416 context,
1417 whole,
1418 area,
1419 };
1420 let when = format!("summary {ordinal}: load");
1421 let (loaded, load) =
1422 load_checked(reader, &world, &part, manager, &when, &mut report.reported)?;
1423 report.summary_loads.extend(load.iter().cloned());
1424 stages.enter(format!("summary {ordinal}: passes"));
1425 let mut part = scoped(loaded, part, load, |_| {}).world;
1426 let mut r = RunReport::default();
1427 let ran = manager.run_part(&mut part, s.summarize, &mut r);
1428 tally(&mut report.counts, &r);
1429 report.summarized.push(r);
1430 ran?;
1431 let summaries = s.registry.take_summaries(&part);
1432 report.summary_bytes += s.registry.retained(&summaries);
1433 kept.push(summaries);
1434 }
1435 stages.enter("decide");
1436 let mut merged = merge(s.registry, manager, &kept)?;
1437 drop(kept);
1438 report.merged_summary_bytes = s.registry.retained(&merged);
1439 manager.verify(&merged, "the merged summaries")?;
1441 manager.run(&mut merged, s.decide, &mut report.decided)?;
1442 tally(&mut report.counts, &report.decided.clone());
1443 if let Some(g) = gathered {
1444 parts.restart()?;
1445 merged = gather(
1446 g,
1447 s.registry,
1448 &merged,
1449 reader,
1450 parts,
1451 &world,
1452 manager,
1453 &target,
1454 &mut writer,
1455 report,
1456 stages,
1457 )?;
1458 }
1459 parts.restart()?;
1460 Some((s.registry, merged))
1461 }
1462 };
1463 while let Some(Part {
1464 roots,
1465 shared,
1466 context,
1467 whole,
1468 area,
1469 }) = parts.next_part()?
1470 {
1471 let ordinal = report.part_reports.len();
1472 stages.enter(format!("part {ordinal}: load"));
1473 let part = Part {
1474 roots,
1475 shared,
1476 context,
1477 whole,
1478 area,
1479 };
1480 let (loaded, load) = load_checked(reader, &world, &part, manager, "load", &mut report.reported)?;
1481 report.loads.extend(load.iter().cloned());
1482 let mut size = PartSize {
1483 roots: part.roots.len(),
1484 loaded: sizes(&loaded),
1485 ..PartSize::default()
1486 };
1487 stages.enter(format!("part {ordinal}: passes"));
1488 report.part_reports.push(PartReport {
1490 ordinal,
1491 roots: part.roots.clone(),
1492 shared: part.shared.clone(),
1493 context: part.context.clone(),
1494 load: load.clone(),
1495 ..PartReport::default()
1496 });
1497 let part_report = report.part_reports.last_mut().expect("pushed");
1498 let loaded = scoped(loaded, part, load, |part| {
1499 if let Some((registry, decided)) = &decided {
1500 registry.hand_on(decided, part);
1501 }
1502 });
1503 let prepared = prepare_loaded(
1504 loaded,
1505 manager,
1506 passes,
1507 &mut part_report.explicit,
1508 &mut part_report.kept,
1509 );
1510 for (k, v) in &part_report.kept {
1511 *report.counts.entry(format!("convert: {k}")).or_default() += v;
1512 }
1513 let part = prepared?;
1514 stages.enter(format!("part {ordinal}: target check"));
1515 (part_report.unsupported, part_report.unsupported_resources) = check_target(&target, &part)?;
1516 let explicit = part_report.explicit.clone();
1517 tally(&mut report.counts, &explicit);
1518 size.written = sizes(&part);
1519 report.part_sizes.push(size);
1520 stages.enter(format!("part {ordinal}: write"));
1521 let written = writer.write(&part);
1522 report.reported.extend(writer.notices());
1523 written?;
1524 parts.written(&part)?;
1525 report.parts += 1;
1526 }
1527 stages.enter("finish");
1528 report.reported.extend(writer.notices());
1529 let finished = writer.finish()?;
1530 report.reported.extend(finished.notices);
1531 report.written = finished.report;
1532 Ok(())
1533}
1534
1535#[allow(clippy::too_many_arguments)]
1538fn gather(
1539 g: &mut Gathered,
1540 registry: &SummaryRegistry,
1541 decided: &World,
1542 reader: &mut dyn Reader,
1543 parts: &mut dyn PartSource,
1544 world: &World,
1545 manager: &PassManager,
1546 target: &Target,
1547 writer: &mut Box<dyn Writer>,
1548 report: &mut ConvertReport,
1549 stages: &mut Stages,
1550) -> Result<World> {
1551 let mut kept = vec![registry.take_summaries(decided)];
1552 let mut handed = World::new();
1553 registry.hand_on(decided, &mut handed);
1554 while let Some(Part {
1557 roots,
1558 shared,
1559 context,
1560 whole,
1561 area,
1562 }) = parts.next_part()?
1563 {
1564 let ordinal = report.gather_runs.len();
1565 stages.enter(format!("gather {ordinal}: load"));
1566 let part = Part {
1567 roots,
1568 shared,
1569 context,
1570 whole,
1571 area,
1572 };
1573 let when = format!("gather {ordinal}: load");
1574 let (loaded, load) = load_checked(reader, world, &part, manager, &when, &mut report.reported)?;
1575 report.gather_loads.extend(load.iter().cloned());
1576 let mut part = scoped(loaded, part, load, |part| registry.hand_on(decided, part)).world;
1577 stages.enter(format!("gather {ordinal}: passes"));
1578 let mut r = RunReport::default();
1579 let ran = manager.run_part(&mut part, g.gather, &mut r);
1580 tally(&mut report.counts, &r);
1581 report.gather_runs.push(r);
1582 ran?;
1583 stages.enter(format!("gather {ordinal}: spool"));
1584 g.spool.take(&mut part)?;
1585 kept.push(registry.take_summaries_made(&part, &handed));
1586 }
1587 while let Some(mut group) = g.spool.next()? {
1588 let ordinal = report.groups.len();
1589 stages.enter(format!("group {ordinal}: load"));
1590 manager.verify(&group, &format!("group {ordinal}"))?;
1591 registry.hand_on(decided, &mut group);
1592 stages.enter(format!("group {ordinal}: passes"));
1593 report.groups.push(GroupReport {
1594 ordinal,
1595 ..GroupReport::default()
1596 });
1597 let group_report = report.groups.last_mut().expect("pushed");
1598 manager.run(&mut group, g.group, &mut group_report.run)?;
1599 tally(&mut report.counts, &report.groups[ordinal].run.clone());
1600 kept.push(registry.take_summaries_made(&group, &handed));
1601 stages.enter(format!("group {ordinal}: target check"));
1602 manager.verify(&group, "the group's passes")?;
1603 let (unsupported, unsupported_resources) = check_target(target, &group)?;
1604 let group_report = &mut report.groups[ordinal];
1605 group_report.unsupported = unsupported;
1606 group_report.unsupported_resources = unsupported_resources;
1607 group_report.written = sizes(&group);
1608 stages.enter(format!("group {ordinal}: write"));
1609 let written = writer.write(&group);
1610 report.reported.extend(writer.notices());
1611 written?;
1612 }
1613 stages.enter("finalize");
1614 let mut merged = merge(registry, manager, &kept)?;
1615 drop(kept);
1616 registry.carry_decisions(decided, &mut merged);
1617 report.merged_summary_bytes = registry.retained(&merged);
1618 manager.verify(&merged, "the merged summaries and groups")?;
1619 manager.run(&mut merged, g.finalize, &mut report.finalized)?;
1620 tally(&mut report.counts, &report.finalized.clone());
1621 Ok(merged)
1622}
1623
1624fn merge(registry: &SummaryRegistry, manager: &PassManager, kept: &[World]) -> Result<World> {
1627 Ok(if manager.pass_checks() {
1628 registry.merge_order_free(kept)?
1629 } else {
1630 registry.merge(kept)?
1631 })
1632}