use std::collections::{BTreeMap, BTreeSet};
use std::time::{Duration, Instant};
use crate::pass::{PassManager, Pipeline, RunReport, Target};
use crate::summary::SummaryRegistry;
use crate::{Entity, Resource, World};
pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Result<T> = std::result::Result<T, Error>;
pub type Report = Vec<(String, String)>;
pub trait Reader {
fn scope_spec(&self) -> Option<&'static crate::read_scope::ReadScopeSpec> {
None
}
fn read_scope(&self) -> Option<crate::read_scope::ReadScope> {
None
}
fn read(&mut self) -> Result<(World, Report)>;
fn load(&mut self, world: &World, roots: &BTreeSet<Entity>) -> Result<(World, Report)>;
fn load_all(&mut self, world: World) -> Result<(World, Report)> {
let roots: BTreeSet<Entity> = world.entities().collect();
self.load(&world, &roots)
}
fn emits(&self) -> &'static [&'static str];
fn prototype_box(&self, world: &World, prototype: Entity) -> Option<[[f64; 3]; 2]>;
fn notices(&mut self) -> Vec<Notice> {
Vec::new()
}
}
fn emitted(name: &str, emits: &[&str]) -> bool {
emits.contains(&name.split('.').next().unwrap_or(""))
}
pub fn check_emits(w: &World, emits: &[&str], when: &str) -> Result<()> {
if let Some(name) = w.component_names().into_iter().find(|n| !emitted(n, emits)) {
return Err(format!("{when}: the reader put {name} in the world (it emits {emits:?})").into());
}
if let Some(name) = w.resource_names().into_iter().find(|n| !emitted(n, emits)) {
return Err(
format!("{when}: the reader put the resource {name} in the world (it emits {emits:?})").into(),
);
}
Ok(())
}
pub fn check_load_emits(selected: &World, part: &World, emits: &[&str]) -> Result<()> {
let before: BTreeSet<&str> = selected.resource_names().into_iter().collect();
if let Some(name) = part
.resource_names()
.into_iter()
.find(|n| !emitted(n, emits) && !before.contains(n))
{
return Err(format!(
"load: the reader put the resource {name} in the part, which the selection had not (it emits {emits:?})"
)
.into());
}
for name in part.component_names() {
if emitted(name, emits) {
continue;
}
let before: BTreeSet<Entity> = selected.entities_named(name).into_iter().collect();
if let Some(e) = part
.entities_named(name)
.into_iter()
.find(|e| !before.contains(e))
{
return Err(format!(
"load: the reader put {name} on {e:?}, which the selection had not (it emits {emits:?})"
)
.into());
}
}
Ok(())
}
fn asked(
roots: &BTreeSet<Entity>,
shared: &BTreeSet<Entity>,
context: &BTreeSet<Entity>,
) -> Result<BTreeSet<Entity>> {
for (set, what) in [(roots, "one of its roots"), (context, "its context")] {
if let Some(e) = shared.intersection(set).next() {
return Err(format!("part source: a part shares {e:?}, which is also {what}").into());
}
}
Ok(roots.iter().chain(shared).chain(context).copied().collect())
}
#[derive(Clone, Debug, PartialEq)]
pub struct Part {
pub roots: BTreeSet<Entity>,
pub shared: BTreeSet<Entity>,
pub context: BTreeSet<Entity>,
pub whole: bool,
pub area: Option<[f64; 4]>,
}
pub trait PartSource {
fn plan(&mut self, world: &World, boxes: &dyn Fn(Entity) -> Option<[[f64; 3]; 2]>) -> Result<()>;
fn next_part(&mut self) -> Result<Option<Part>>;
fn written(&mut self, _part: &World) -> Result<()> {
Ok(())
}
fn restart(&mut self) -> Result<()> {
Err("this part source gives its parts once: it cannot be gone over twice".into())
}
}
#[derive(Debug, Default)]
pub struct WholeWorld {
planned: BTreeSet<Entity>,
given: bool,
}
impl PartSource for WholeWorld {
fn plan(&mut self, world: &World, _boxes: &dyn Fn(Entity) -> Option<[[f64; 3]; 2]>) -> Result<()> {
self.planned = world.entities().collect();
self.given = false;
Ok(())
}
fn next_part(&mut self) -> Result<Option<Part>> {
if self.given {
return Ok(None);
}
self.given = true;
Ok(Some(Part {
roots: self.planned.clone(),
shared: BTreeSet::new(),
context: BTreeSet::new(),
whole: true,
area: None,
}))
}
fn restart(&mut self) -> Result<()> {
self.given = false;
Ok(())
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PartitionPlan {
pub by: String,
pub groups: Vec<PartitionGroup>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PartitionGroup {
pub key: String,
pub roots: BTreeSet<Entity>,
pub context: BTreeSet<Entity>,
pub area: Option<[f64; 4]>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PartScope {
pub roots: BTreeSet<Entity>,
pub shared: BTreeSet<Entity>,
pub context: BTreeSet<Entity>,
pub whole: bool,
pub area: Option<[f64; 4]>,
pub first_made: Option<u32>,
pub placed: BTreeMap<Entity, bool>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PartOutput {
pub keep: BTreeSet<Entity>,
pub placed: BTreeSet<Entity>,
pub from_context: BTreeSet<Entity>,
}
impl PartScope {
pub fn output(&self, w: &World) -> PartOutput {
let mut out = PartOutput::default();
for (&e, &own) in self.placed.iter().filter(|(e, _)| w.is_alive(**e)) {
if own {
out.placed.insert(e);
} else {
out.from_context.insert(e);
}
}
out.keep = w.closure(self.roots.iter().chain(&self.shared).chain(&out.placed).copied());
out
}
pub fn owned(&self, w: &World) -> BTreeSet<Entity> {
let own = self.placed.iter().filter(|(e, own)| **own && w.is_alive(**e));
let mut owned = w.closure(self.roots.iter().copied().chain(own.map(|(e, _)| *e)));
owned.retain(|e| !self.shared.contains(e));
owned
}
pub fn declare(
w: &mut World,
placed: &BTreeSet<Entity>,
placed_from: &BTreeMap<Entity, Entity>,
owned_before: &BTreeSet<Entity>,
) {
let Some(scope) = w.resource::<PartScope>() else {
return;
};
if placed.is_empty() {
return;
}
let shared = scope.shared.clone();
let mut owned = scope.owned(w);
owned.extend(owned_before.difference(&shared));
let mut own: BTreeSet<Entity> = BTreeSet::new();
loop {
let found: Vec<Entity> = placed
.iter()
.filter(|e| !own.contains(e))
.filter(|e| placed_from.get(e).is_none_or(|s| owned.contains(s)))
.copied()
.collect();
if found.is_empty() {
break;
}
owned.extend(found.iter().copied());
owned.extend(w.closure(found.iter().copied()).difference(&shared));
own.extend(found);
}
let classified: Vec<(Entity, bool)> = placed.iter().map(|e| (*e, own.contains(e))).collect();
if let Some(scope) = w.resource_mut::<PartScope>() {
scope.placed.extend(classified);
}
}
}
#[derive(Debug)]
pub struct LoadedPart {
pub world: World,
pub load: Report,
loaded: BTreeSet<Entity>,
}
#[derive(Debug)]
pub struct PreparedPart {
pub world: World,
pub report: PartReport,
}
fn load_checked(
reader: &mut dyn Reader,
world: &World,
part: &Part,
manager: &PassManager,
when: &str,
notices: &mut Vec<Notice>,
) -> Result<(World, Report)> {
let asked = asked(&part.roots, &part.shared, &part.context)?;
let loaded = reader.load(world, &asked);
notices.extend(reader.notices());
let (loaded, report) = loaded?;
check_load_emits(world, &loaded, reader.emits())?;
manager.verify(&loaded, when)?;
Ok((loaded, report))
}
fn scoped(mut loaded: World, part: Part, load: Report, hand_on: impl FnOnce(&mut World)) -> LoadedPart {
let entities: BTreeSet<Entity> = loaded.entities().collect();
hand_on(&mut loaded);
let first_made = Some(loaded.next_index());
let Part {
roots,
shared,
context,
whole,
area,
} = part;
loaded.insert_resource(PartScope {
roots,
shared,
context,
whole,
area,
first_made,
placed: BTreeMap::new(),
});
LoadedPart {
world: loaded,
load,
loaded: entities,
}
}
pub fn load_part(
reader: &mut dyn Reader,
world: &World,
part: Part,
manager: &PassManager,
notices: &mut Vec<Notice>,
) -> Result<LoadedPart> {
let (loaded, load) = load_checked(reader, world, &part, manager, "load", notices)?;
Ok(scoped(loaded, part, load, |_| {}))
}
pub fn prepare_loaded(
loaded: LoadedPart,
manager: &PassManager,
passes: &Pipeline,
explicit: &mut RunReport,
kept: &mut BTreeMap<String, u64>,
) -> Result<World> {
let LoadedPart {
world: mut part,
loaded,
..
} = loaded;
manager.run_part(&mut part, passes, explicit)?;
let scope = part
.remove_resource::<PartScope>()
.ok_or("the part's scope was taken out by its passes")?;
let PartOutput {
keep,
placed,
from_context,
} = scope.output(&part);
let names: Vec<String> = part.component_names().into_iter().map(str::to_owned).collect();
let kinds = |e: Entity| {
names
.iter()
.filter(|n| part.entity_has_named(e, n))
.map(String::as_str)
.collect::<Vec<_>>()
.join(" + ")
};
for e in part.entities().filter(|e| !loaded.contains(e)) {
let what = if placed.contains(&e) {
"entities the passes placed"
} else if from_context.contains(&e) {
"entities placed from context, left to the part that owns their source"
} else if keep.contains(&e) {
continue;
} else {
"made entities nothing keeps, dropped"
};
*kept.entry(format!("{what} ({})", kinds(e))).or_default() += 1;
}
part.retain(&keep);
manager.verify(&part, "the passes")?;
Ok(part)
}
pub fn prepare_part(
reader: &mut dyn Reader,
world: &World,
part: Part,
manager: &PassManager,
passes: &Pipeline,
notices: &mut Vec<Notice>,
) -> Result<PreparedPart> {
let mut report = PartReport {
roots: part.roots.clone(),
shared: part.shared.clone(),
context: part.context.clone(),
..PartReport::default()
};
let loaded = load_part(reader, world, part, manager, notices)?;
report.load = loaded.load.clone();
let world = prepare_loaded(loaded, manager, passes, &mut report.explicit, &mut report.kept)?;
Ok(PreparedPart { world, report })
}
impl Resource for PartScope {
const NAME: &'static str = "core.part_scope";
fn references(&self, out: &mut Vec<Entity>) {
out.extend(
self.roots
.iter()
.chain(&self.shared)
.chain(&self.context)
.copied(),
);
}
fn retain_references(&mut self, keep: &dyn Fn(Entity) -> bool) {
self.roots.retain(|&e| keep(e));
self.shared.retain(|&e| keep(e));
self.context.retain(|&e| keep(e));
}
}
pub const OPERATIONAL_RESOURCES: &[&str] = &[
PartitionPlan::NAME,
PartScope::NAME,
<primase_foundations::parallel::Threads as Resource>::NAME,
];
impl Resource for PartitionPlan {
const NAME: &'static str = "core.partition_plan";
fn to_json(&self) -> std::result::Result<Option<serde_json::Value>, crate::JsonError> {
let groups: Vec<serde_json::Value> = self
.groups
.iter()
.map(|g| {
serde_json::json!({
"key": g.key,
"roots": g.roots.iter().map(|e| e.index()).collect::<Vec<_>>(),
"context": g.context.iter().map(|e| e.index()).collect::<Vec<_>>(),
"area": g.area,
})
})
.collect();
Ok(Some(serde_json::json!({ "by": self.by, "groups": groups })))
}
fn verify(&self, _w: &World) -> std::result::Result<(), String> {
let mut keys = BTreeSet::new();
match self.groups.iter().find(|g| !keys.insert(g.key.as_str())) {
Some(g) => Err(format!("partition group key {:?} twice", g.key)),
None => Ok(()),
}
}
fn references(&self, out: &mut Vec<Entity>) {
for g in &self.groups {
out.extend(g.roots.iter().chain(&g.context).copied());
}
}
fn retain_references(&mut self, keep: &dyn Fn(Entity) -> bool) {
for g in &mut self.groups {
g.roots.retain(|&e| keep(e));
g.context.retain(|&e| keep(e));
}
}
}
#[derive(Debug, Default)]
pub struct PlannedParts {
planned: Vec<PartitionGroup>,
groups: std::vec::IntoIter<PartitionGroup>,
single: bool,
}
fn ground_box(t: &crate::Transform, b: [[f64; 3]; 2]) -> [f64; 4] {
let mut out = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
for k in 0..8 {
let c = [b[k & 1][0], b[(k >> 1) & 1][1], b[(k >> 2) & 1][2]];
let p: [f64; 3] = std::array::from_fn(|i| {
(0..3).map(|j| f64::from(t.cols[j][i]) * c[j]).sum::<f64>() + f64::from(t.cols[3][i])
});
let (east, north) = (p[0], -p[2]);
out = [
out[0].min(east),
out[1].min(north),
out[2].max(east),
out[3].max(north),
];
}
out
}
impl PartSource for PlannedParts {
fn plan(&mut self, world: &World, boxes: &dyn Fn(Entity) -> Option<[[f64; 3]; 2]>) -> Result<()> {
let plan = world
.resource::<PartitionPlan>()
.ok_or("no core.partition_plan in the selected world: run a partition pass in the selection")?;
let placed: Vec<(Entity, [f64; 4])> = world
.iter::<crate::Instance>()
.filter_map(|(e, i)| {
let t = world.get::<crate::Transform>(e)?;
Some((e, ground_box(t, boxes(i.prototype)?)))
})
.collect();
let prototypes: BTreeSet<Entity> = world
.iter::<crate::Instance>()
.map(|(_, i)| i.prototype)
.collect();
let soups: Vec<(Entity, [f64; 4])> = world
.iter::<crate::Collision>()
.filter(|(e, c)| !prototypes.contains(e) && !c.positions.is_empty())
.map(|(e, c)| {
let t = world
.get::<crate::Transform>(e)
.copied()
.unwrap_or(crate::Transform::IDENTITY);
let mut b = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
for p in &c.positions {
let q = t.apply(*p);
let (east, north) = (f64::from(q[0]), -f64::from(q[2]));
b = [b[0].min(east), b[1].min(north), b[2].max(east), b[3].max(north)];
}
(e, b)
})
.collect();
let mut groups = plan.groups.clone();
for g in &mut groups {
let Some(a) = g.area else { continue };
for &(e, b) in placed.iter().chain(&soups) {
if !g.roots.contains(&e) && b[0] < a[2] && b[2] >= a[0] && b[1] < a[3] && b[3] >= a[1] {
g.context.insert(e);
}
}
}
self.single = groups.len() == 1;
self.planned = groups.clone();
self.groups = groups.into_iter();
Ok(())
}
fn restart(&mut self) -> Result<()> {
self.groups = self.planned.clone().into_iter();
Ok(())
}
fn next_part(&mut self) -> Result<Option<Part>> {
let single = self.single;
Ok(self.groups.next().map(|g| Part {
whole: single && g.context.is_empty(),
roots: g.roots,
shared: BTreeSet::new(),
context: g.context,
area: g.area,
}))
}
}
pub trait Writer {
fn target(&self) -> Target;
fn write(&mut self, part: &World) -> Result<()>;
fn notices(&mut self) -> Vec<Notice> {
Vec::new()
}
fn finish(self: Box<Self>) -> Result<Finished>;
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Finished {
pub report: Report,
pub notices: Vec<Notice>,
}
impl From<Report> for Finished {
fn from(report: Report) -> Finished {
Finished {
report,
notices: Vec::new(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum NoticeKind {
Unsupported,
Approximation,
Selection,
FailureContext,
Diagnostic,
}
pub const NOTICE_EXAMPLES: usize = 8;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Notice {
pub stage: String,
pub kind: NoticeKind,
pub reason: String,
pub count: u64,
pub examples: Vec<String>,
pub entities: Vec<Entity>,
}
impl Notice {
pub fn new(stage: impl Into<String>, kind: NoticeKind, reason: impl Into<String>, count: u64) -> Notice {
Notice {
stage: stage.into(),
kind,
reason: reason.into(),
count,
examples: Vec::new(),
entities: Vec::new(),
}
}
pub fn example(mut self, e: impl Into<String>) -> Notice {
if self.examples.len() < NOTICE_EXAMPLES {
self.examples.push(e.into());
}
self
}
}
impl std::fmt::Display for Notice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let kind = match self.kind {
NoticeKind::Unsupported => "unsupported",
NoticeKind::Approximation => "approximation",
NoticeKind::Selection => "selection",
NoticeKind::FailureContext => "failure",
NoticeKind::Diagnostic => "diagnostic",
};
write!(f, "{kind} {}: {} x {}", self.stage, self.reason, self.count)?;
if !self.examples.is_empty() {
write!(f, " ({})", self.examples.join(", "))?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct PartReport {
pub ordinal: usize,
pub roots: BTreeSet<Entity>,
pub shared: BTreeSet<Entity>,
pub context: BTreeSet<Entity>,
pub load: Report,
pub explicit: RunReport,
pub unsupported: Vec<(String, u64)>,
pub unsupported_resources: Vec<String>,
pub kept: BTreeMap<String, u64>,
}
#[derive(Clone, Debug, Default)]
pub struct GroupReport {
pub ordinal: usize,
pub run: RunReport,
pub unsupported: Vec<(String, u64)>,
pub unsupported_resources: Vec<String>,
pub written: BTreeMap<String, u64>,
}
#[derive(Debug, Default)]
pub struct ConvertReport {
pub read: Report,
pub select: RunReport,
pub loads: Report,
pub summary_loads: Report,
pub summarized: Vec<RunReport>,
pub decided: RunReport,
pub groups: Vec<GroupReport>,
pub finalized: RunReport,
pub gather_loads: Report,
pub gather_runs: Vec<RunReport>,
pub summary_bytes: usize,
pub merged_summary_bytes: usize,
pub parts: usize,
pub part_sizes: Vec<PartSize>,
pub counts: BTreeMap<String, u64>,
pub part_reports: Vec<PartReport>,
pub reported: Vec<Notice>,
pub written: Report,
pub stages: Vec<(String, Duration)>,
}
impl ConvertReport {
pub fn approximations(&self) -> BTreeMap<String, u64> {
let mut out = BTreeMap::new();
let runs = [&self.select, &self.decided, &self.finalized]
.into_iter()
.chain(self.groups.iter().map(|g| &g.run))
.chain(self.part_reports.iter().map(|p| &p.explicit));
for run in runs {
for step in &run.steps {
for (k, v) in &step.log.approximations {
*out.entry(format!("{}: {k}", step.pass)).or_default() += v;
}
}
}
for (traversal, runs) in self.preparing_traversals() {
for step in runs.iter().flat_map(|r| &r.steps) {
for (k, v) in &step.log.approximations {
*out.entry(format!("{} ({traversal}): {k}", step.pass))
.or_default() += v;
}
}
}
out
}
fn preparing_traversals(&self) -> [(&'static str, &Vec<RunReport>); 2] {
[
("summary traversal", &self.summarized),
("gather traversal", &self.gather_runs),
]
}
pub fn notices(&self) -> Vec<Notice> {
let mut out = self.reported.clone();
let mut derived: BTreeMap<(String, NoticeKind, String), (u64, Vec<String>)> = BTreeMap::new();
let attributed: std::cell::RefCell<BTreeMap<(String, NoticeKind, String), BTreeSet<Entity>>> =
std::cell::RefCell::new(BTreeMap::new());
let mut add = |stage: &str, kind: NoticeKind, reason: &str, n: u64, part: Option<String>| {
let (count, examples) = derived
.entry((stage.to_owned(), kind, reason.to_owned()))
.or_default();
*count += n;
if let Some(at) = part
&& examples.len() < NOTICE_EXAMPLES
{
examples.push(at);
}
};
let whole = [&self.select, &self.decided, &self.finalized]
.into_iter()
.map(|r| (r, None))
.chain(
self.groups
.iter()
.map(|g| (&g.run, Some(format!("group {}", g.ordinal)))),
);
for (run, at) in whole {
for step in &run.steps {
for (k, v) in &step.log.approximations {
add(&step.pass, NoticeKind::Approximation, k, *v, at.clone());
if at.is_none()
&& let Some(on) = step.log.approximated_on.get(k)
{
attributed
.borrow_mut()
.entry((step.pass.clone(), NoticeKind::Approximation, k.clone()))
.or_default()
.extend(on.iter().copied());
}
}
for (k, v) in &step.log.omitted {
add(&step.pass, NoticeKind::Unsupported, k, *v, at.clone());
}
for ((m, why), v) in &step.log.drops {
add(
&step.pass,
NoticeKind::Selection,
&format!("{m}: {why}"),
*v,
at.clone(),
);
}
for g in &step.conservation {
add(&step.pass, NoticeKind::Diagnostic, &g.to_string(), 1, at.clone());
}
}
}
for (traversal, runs) in self.preparing_traversals() {
for (i, run) in runs.iter().enumerate() {
for step in &run.steps {
let stage = format!("{} ({traversal})", step.pass);
let at = || Some(format!("part {i}"));
for (k, v) in &step.log.approximations {
add(&stage, NoticeKind::Approximation, k, *v, at());
}
for (k, v) in &step.log.omitted {
add(&stage, NoticeKind::Unsupported, k, *v, at());
}
for ((m, why), v) in &step.log.drops {
add(&stage, NoticeKind::Selection, &format!("{m}: {why}"), *v, at());
}
for g in &step.conservation {
add(&stage, NoticeKind::Diagnostic, &g.to_string(), 1, at());
}
}
}
}
for g in &self.groups {
for (name, n) in &g.unsupported {
add(
"convert",
NoticeKind::Unsupported,
&format!("{name}: the writer doesn't write it"),
*n,
Some(format!("group {}", g.ordinal)),
);
}
for name in &g.unsupported_resources {
add(
"convert",
NoticeKind::Unsupported,
&format!("resource {name}: the writer doesn't write it"),
1,
Some(format!("group {}", g.ordinal)),
);
}
}
for p in &self.part_reports {
for step in &p.explicit.steps {
for (k, v) in &step.log.approximations {
add(
&step.pass,
NoticeKind::Approximation,
k,
*v,
Some(format!("part {}", p.ordinal)),
);
}
for (k, v) in &step.log.omitted {
add(
&step.pass,
NoticeKind::Unsupported,
k,
*v,
Some(format!("part {}", p.ordinal)),
);
}
for ((m, why), v) in &step.log.drops {
add(
&step.pass,
NoticeKind::Selection,
&format!("{m}: {why}"),
*v,
Some(format!("part {}", p.ordinal)),
);
}
for g in &step.conservation {
add(
&step.pass,
NoticeKind::Diagnostic,
&g.to_string(),
1,
Some(format!("part {}", p.ordinal)),
);
}
}
for (k, v) in &p.kept {
if k.starts_with("made entities nothing keeps") {
add(
"convert",
NoticeKind::Selection,
k,
*v,
Some(format!("part {}", p.ordinal)),
);
}
}
for (name, n) in &p.unsupported {
add(
"convert",
NoticeKind::Unsupported,
&format!("{name}: the writer doesn't write it"),
*n,
Some(format!("part {}", p.ordinal)),
);
}
for name in &p.unsupported_resources {
add(
"convert",
NoticeKind::Unsupported,
&format!("resource {name}: the writer doesn't write it"),
1,
Some(format!("part {}", p.ordinal)),
);
}
}
out.extend(
derived
.into_iter()
.map(|((stage, kind, reason), (count, examples))| Notice {
entities: attributed
.borrow()
.get(&(stage.clone(), kind, reason.clone()))
.map(|e| e.iter().copied().take(NOTICE_EXAMPLES).collect())
.unwrap_or_default(),
stage,
kind,
reason,
count,
examples,
}),
);
out
}
}
#[derive(Debug)]
pub struct ConvertError {
pub error: Error,
pub during: String,
pub partial: Box<ConvertReport>,
}
impl std::fmt::Display for ConvertError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.error.fmt(f)
}
}
impl std::error::Error for ConvertError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.error.as_ref())
}
}
#[derive(Debug)]
struct Stages {
during: String,
started: Instant,
done: Vec<(String, Duration)>,
}
impl Stages {
fn new() -> Stages {
Stages {
during: String::new(),
started: Instant::now(),
done: Vec::new(),
}
}
fn enter(&mut self, stage: impl Into<String>) {
self.close();
self.during = stage.into();
}
fn close(&mut self) {
if !self.during.is_empty() {
self.done.push((self.during.clone(), self.started.elapsed()));
}
self.started = Instant::now();
}
}
fn finish(mut report: ConvertReport, mut stages: Stages, ran: Result<()>) -> Result<ConvertReport> {
stages.close();
report.stages = std::mem::take(&mut stages.done);
match ran {
Ok(()) => Ok(report),
Err(error) => {
let during = stages.during;
report.reported.push(Notice::new(
"convert",
NoticeKind::FailureContext,
format!("failed during {during}: {error}"),
1,
));
Err(Box::new(ConvertError {
error,
during,
partial: Box::new(report),
}))
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PartSize {
pub roots: usize,
pub loaded: BTreeMap<String, u64>,
pub written: BTreeMap<String, u64>,
}
fn sizes(w: &World) -> BTreeMap<String, u64> {
w.payload_bytes()
.into_iter()
.map(|(n, b)| (n.to_owned(), b))
.collect()
}
fn tally(counts: &mut BTreeMap<String, u64>, report: &RunReport) {
for step in &report.steps {
for (k, v) in &step.log.counts {
*counts.entry(format!("{}: {k}", step.pass)).or_default() += v;
}
}
}
pub fn convert_parts(
reader: &mut dyn Reader,
parts: &mut dyn PartSource,
writer: Box<dyn Writer>,
manager: &PassManager,
select: &Pipeline,
passes: &Pipeline,
) -> Result<ConvertReport> {
let mut report = ConvertReport::default();
let mut stages = Stages::new();
let ran = run(
reader,
parts,
writer,
manager,
select,
None,
passes,
&mut report,
&mut stages,
);
finish(report, stages, ran)
}
#[derive(Debug)]
pub struct Summarized<'a> {
pub registry: &'a SummaryRegistry,
pub summarize: &'a Pipeline,
pub decide: &'a Pipeline,
}
#[allow(clippy::too_many_arguments)]
pub fn convert_parts_summarized(
reader: &mut dyn Reader,
parts: &mut dyn PartSource,
writer: Box<dyn Writer>,
manager: &PassManager,
select: &Pipeline,
summarized: &Summarized,
passes: &Pipeline,
) -> Result<ConvertReport> {
let mut report = ConvertReport::default();
let mut stages = Stages::new();
let ran = run(
reader,
parts,
writer,
manager,
select,
Some((summarized, None)),
passes,
&mut report,
&mut stages,
);
finish(report, stages, ran)
}
pub trait Spool {
fn take(&mut self, part: &mut World) -> Result<()>;
fn next(&mut self) -> Result<Option<World>>;
}
pub struct Gathered<'a> {
pub spool: &'a mut dyn Spool,
pub gather: &'a Pipeline,
pub group: &'a Pipeline,
pub finalize: &'a Pipeline,
}
impl std::fmt::Debug for Gathered<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Gathered")
.field("gather", self.gather)
.field("group", self.group)
.field("finalize", self.finalize)
.finish_non_exhaustive()
}
}
#[allow(clippy::too_many_arguments)]
pub fn convert_parts_gathered(
reader: &mut dyn Reader,
parts: &mut dyn PartSource,
writer: Box<dyn Writer>,
manager: &PassManager,
select: &Pipeline,
summarized: &Summarized,
gathered: Gathered,
passes: &Pipeline,
) -> Result<ConvertReport> {
let mut report = ConvertReport::default();
let mut stages = Stages::new();
let mut gathered = gathered;
let ran = run(
reader,
parts,
writer,
manager,
select,
Some((summarized, Some(&mut gathered))),
passes,
&mut report,
&mut stages,
);
finish(report, stages, ran)
}
type NotWritten = (Vec<(String, u64)>, Vec<String>);
fn check_target(target: &Target, w: &World) -> Result<NotWritten> {
let checked = target.check_prepared(w);
if let Some((name, words, bad)) = checked.invalid.first() {
return Err(format!(
"{}: {} of {name} the writer can't write ({words}), the first {:?}",
target.name,
bad.len(),
bad[0]
)
.into());
}
if let Some(name) = checked.invalid_resources.first() {
return Err(format!(
"{}: resource {name} refused ({})",
target.name,
target.rule_words(name).unwrap_or_default()
)
.into());
}
Ok((checked.unsupported, checked.unsupported_resources))
}
pub fn convert(
reader: &mut dyn Reader,
writer: Box<dyn Writer>,
manager: &PassManager,
select: &Pipeline,
passes: &Pipeline,
) -> Result<ConvertReport> {
convert_parts(
reader,
&mut WholeWorld::default(),
writer,
manager,
select,
passes,
)
}
#[allow(clippy::too_many_arguments)]
fn run(
reader: &mut dyn Reader,
parts: &mut dyn PartSource,
mut writer: Box<dyn Writer>,
manager: &PassManager,
select: &Pipeline,
summarized: Option<(&Summarized, Option<&mut Gathered>)>,
passes: &Pipeline,
report: &mut ConvertReport,
stages: &mut Stages,
) -> Result<()> {
stages.enter("read");
let read = reader.read();
report.reported.extend(reader.notices());
let (mut world, read) = read?;
report.read = read;
check_emits(&world, reader.emits(), "read")?;
manager.verify(&world, "read")?;
stages.enter("select");
manager.run(&mut world, select, &mut report.select)?;
let target = writer.target();
stages.enter("plan");
parts.plan(&world, &|p| reader.prototype_box(&world, p))?;
let decided = match summarized {
None => None,
Some((s, gathered)) => {
let mut kept = Vec::new();
while let Some(Part {
roots,
shared,
context,
whole,
area,
}) = parts.next_part()?
{
let ordinal = kept.len();
stages.enter(format!("summary {ordinal}: load"));
let part = Part {
roots,
shared,
context,
whole,
area,
};
let when = format!("summary {ordinal}: load");
let (loaded, load) =
load_checked(reader, &world, &part, manager, &when, &mut report.reported)?;
report.summary_loads.extend(load.iter().cloned());
stages.enter(format!("summary {ordinal}: passes"));
let mut part = scoped(loaded, part, load, |_| {}).world;
let mut r = RunReport::default();
let ran = manager.run_part(&mut part, s.summarize, &mut r);
tally(&mut report.counts, &r);
report.summarized.push(r);
ran?;
let summaries = s.registry.take_summaries(&part);
report.summary_bytes += s.registry.retained(&summaries);
kept.push(summaries);
}
stages.enter("decide");
let mut merged = merge(s.registry, manager, &kept)?;
drop(kept);
report.merged_summary_bytes = s.registry.retained(&merged);
manager.verify(&merged, "the merged summaries")?;
manager.run(&mut merged, s.decide, &mut report.decided)?;
tally(&mut report.counts, &report.decided.clone());
if let Some(g) = gathered {
parts.restart()?;
merged = gather(
g,
s.registry,
&merged,
reader,
parts,
&world,
manager,
&target,
&mut writer,
report,
stages,
)?;
}
parts.restart()?;
Some((s.registry, merged))
}
};
while let Some(Part {
roots,
shared,
context,
whole,
area,
}) = parts.next_part()?
{
let ordinal = report.part_reports.len();
stages.enter(format!("part {ordinal}: load"));
let part = Part {
roots,
shared,
context,
whole,
area,
};
let (loaded, load) = load_checked(reader, &world, &part, manager, "load", &mut report.reported)?;
report.loads.extend(load.iter().cloned());
let mut size = PartSize {
roots: part.roots.len(),
loaded: sizes(&loaded),
..PartSize::default()
};
stages.enter(format!("part {ordinal}: passes"));
report.part_reports.push(PartReport {
ordinal,
roots: part.roots.clone(),
shared: part.shared.clone(),
context: part.context.clone(),
load: load.clone(),
..PartReport::default()
});
let part_report = report.part_reports.last_mut().expect("pushed");
let loaded = scoped(loaded, part, load, |part| {
if let Some((registry, decided)) = &decided {
registry.hand_on(decided, part);
}
});
let prepared = prepare_loaded(
loaded,
manager,
passes,
&mut part_report.explicit,
&mut part_report.kept,
);
for (k, v) in &part_report.kept {
*report.counts.entry(format!("convert: {k}")).or_default() += v;
}
let part = prepared?;
stages.enter(format!("part {ordinal}: target check"));
(part_report.unsupported, part_report.unsupported_resources) = check_target(&target, &part)?;
let explicit = part_report.explicit.clone();
tally(&mut report.counts, &explicit);
size.written = sizes(&part);
report.part_sizes.push(size);
stages.enter(format!("part {ordinal}: write"));
let written = writer.write(&part);
report.reported.extend(writer.notices());
written?;
parts.written(&part)?;
report.parts += 1;
}
stages.enter("finish");
report.reported.extend(writer.notices());
let finished = writer.finish()?;
report.reported.extend(finished.notices);
report.written = finished.report;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn gather(
g: &mut Gathered,
registry: &SummaryRegistry,
decided: &World,
reader: &mut dyn Reader,
parts: &mut dyn PartSource,
world: &World,
manager: &PassManager,
target: &Target,
writer: &mut Box<dyn Writer>,
report: &mut ConvertReport,
stages: &mut Stages,
) -> Result<World> {
let mut kept = vec![registry.take_summaries(decided)];
let mut handed = World::new();
registry.hand_on(decided, &mut handed);
while let Some(Part {
roots,
shared,
context,
whole,
area,
}) = parts.next_part()?
{
let ordinal = report.gather_runs.len();
stages.enter(format!("gather {ordinal}: load"));
let part = Part {
roots,
shared,
context,
whole,
area,
};
let when = format!("gather {ordinal}: load");
let (loaded, load) = load_checked(reader, world, &part, manager, &when, &mut report.reported)?;
report.gather_loads.extend(load.iter().cloned());
let mut part = scoped(loaded, part, load, |part| registry.hand_on(decided, part)).world;
stages.enter(format!("gather {ordinal}: passes"));
let mut r = RunReport::default();
let ran = manager.run_part(&mut part, g.gather, &mut r);
tally(&mut report.counts, &r);
report.gather_runs.push(r);
ran?;
stages.enter(format!("gather {ordinal}: spool"));
g.spool.take(&mut part)?;
kept.push(registry.take_summaries_made(&part, &handed));
}
while let Some(mut group) = g.spool.next()? {
let ordinal = report.groups.len();
stages.enter(format!("group {ordinal}: load"));
manager.verify(&group, &format!("group {ordinal}"))?;
registry.hand_on(decided, &mut group);
stages.enter(format!("group {ordinal}: passes"));
report.groups.push(GroupReport {
ordinal,
..GroupReport::default()
});
let group_report = report.groups.last_mut().expect("pushed");
manager.run(&mut group, g.group, &mut group_report.run)?;
tally(&mut report.counts, &report.groups[ordinal].run.clone());
kept.push(registry.take_summaries_made(&group, &handed));
stages.enter(format!("group {ordinal}: target check"));
manager.verify(&group, "the group's passes")?;
let (unsupported, unsupported_resources) = check_target(target, &group)?;
let group_report = &mut report.groups[ordinal];
group_report.unsupported = unsupported;
group_report.unsupported_resources = unsupported_resources;
group_report.written = sizes(&group);
stages.enter(format!("group {ordinal}: write"));
let written = writer.write(&group);
report.reported.extend(writer.notices());
written?;
}
stages.enter("finalize");
let mut merged = merge(registry, manager, &kept)?;
drop(kept);
registry.carry_decisions(decided, &mut merged);
report.merged_summary_bytes = registry.retained(&merged);
manager.verify(&merged, "the merged summaries and groups")?;
manager.run(&mut merged, g.finalize, &mut report.finalized)?;
tally(&mut report.counts, &report.finalized.clone());
Ok(merged)
}
fn merge(registry: &SummaryRegistry, manager: &PassManager, kept: &[World]) -> Result<World> {
Ok(if manager.pass_checks() {
registry.merge_order_free(kept)?
} else {
registry.merge(kept)?
})
}