use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use crate::sdf::expr;
use crate::sdf::schema::{ChildrenKey, FieldKey};
use crate::sdf::{AbstractData, LayerOffset, Path, Value};
use super::mapping::MapFunction;
use super::prim_graph::{is_class_based_arc, ArcType, NodeFlags, NodeId, PrimIndexGraph};
use super::prim_index::{
collect_payloads_in, compose_arc_list_in, compose_references_in, CompositionContext, PrimIndex,
};
use super::{CycleChain, CycleHop, Error, LayerGraph, LayerId};
const MAX_DEPTH: usize = 100;
pub(crate) type BuildResult<T> = std::result::Result<T, BuildError>;
#[derive(Debug, thiserror::Error)]
#[error("{message}")]
pub(crate) struct BuildError {
message: String,
}
impl From<anyhow::Error> for BuildError {
fn from(error: anyhow::Error) -> Self {
Self {
message: format!("{error:#}"),
}
}
}
fn find_containing_variant_selection(path: &Path) -> Option<Path> {
let mut p = path.clone();
loop {
if p.is_prim_variant_selection_path() {
return Some(p);
}
p = p.parent()?;
}
}
fn variant_storage_site(node_path: &Path, stripped: &Path) -> Option<Path> {
let mut p = node_path.clone();
loop {
if p.strip_all_variant_selections() == *stripped {
return Some(p);
}
p = p.parent()?;
}
}
fn determine_inherit_path(parent_path: &Path, inherit_map: &MapFunction) -> Option<Path> {
if !parent_path.contains_prim_variant_selection() {
return inherit_map.map_target_to_source(parent_path);
}
let var_path = find_containing_variant_selection(parent_path)?;
let stripped = parent_path.strip_all_variant_selections();
let mapped = inherit_map.map_target_to_source(&stripped)?;
Some(
mapped
.replace_prefix(&var_path.strip_all_variant_selections(), &var_path)
.unwrap_or(mapped),
)
}
struct Frame<'f> {
requested_path: Path,
root_path: Path,
graph: &'f PrimIndexGraph,
arc: ArcType,
requested_layer: LayerId,
skip: bool,
previous: Option<&'f Frame<'f>>,
}
struct FrameSites<'f> {
search: Path,
current_root: Path,
frame: Option<&'f Frame<'f>>,
}
impl<'f> Iterator for FrameSites<'f> {
type Item = (&'f Frame<'f>, Path);
fn next(&mut self) -> Option<Self::Item> {
let f = self.frame?;
self.search = if self.current_root == self.search {
f.requested_path.clone()
} else {
f.requested_path
.replace_prefix(&self.current_root, &self.search)
.unwrap_or_else(|| f.requested_path.clone())
};
self.current_root = f.root_path.clone();
self.frame = f.previous;
Some((f, self.search.clone()))
}
}
#[derive(Clone, PartialEq, Eq)]
struct Task {
kind: TaskKind,
node: NodeId,
variant: Option<VariantTask>,
}
impl Task {
fn new(kind: TaskKind, node: NodeId) -> Self {
Task {
kind,
node,
variant: None,
}
}
fn variant(kind: TaskKind, node: NodeId, vt: VariantTask) -> Self {
Task {
kind,
node,
variant: Some(vt),
}
}
}
#[derive(Clone, PartialEq, Eq)]
struct VariantTask {
vset_path: Path,
vset_name: String,
vset_num: u16,
}
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum TaskKind {
EvalNodeVariantNoneFound,
EvalNodeVariantFallback,
EvalNodeVariantAuthored,
EvalNodeVariantSets,
EvalNodeAncestralVariantNoneFound,
EvalNodeAncestralVariantFallback,
EvalNodeAncestralVariantAuthored,
EvalNodeAncestralVariantSets,
EvalImpliedClasses,
EvalImpliedSpecializes,
EvalNodeSpecializes,
EvalNodeInherits,
EvalNodePayloads,
EvalNodeReferences,
EvalImpliedRelocations,
EvalNodeRelocations,
}
pub(crate) struct BuildOutput {
pub(crate) graph: Option<PrimIndexGraph>,
pub(crate) errors: Vec<Error>,
}
#[derive(Clone, Copy)]
struct Inputs<'a> {
stack: &'a LayerGraph,
ctx: &'a CompositionContext,
cached_indices: &'a HashMap<Path, PrimIndex>,
}
struct Site<'a> {
ambient: &'a [(LayerId, LayerOffset)],
is_root: bool,
path: Path,
}
pub(crate) struct Indexer<'a, 'f> {
inputs: Inputs<'a>,
site: Site<'a>,
frame: Option<&'f Frame<'f>>,
frame_depth: usize,
output: PrimIndexGraph,
tasks: Vec<Task>,
implied_seen: HashSet<(NodeId, TaskKind)>,
root_contributes: bool,
errors: Vec<Error>,
pending_relocation_errors: Vec<(NodeId, Error)>,
}
impl<'a, 'f> Indexer<'a, 'f> {
pub(crate) fn new(
stack: &'a LayerGraph,
ctx: &'a CompositionContext,
cached_indices: &'a HashMap<Path, PrimIndex>,
ambient: &'a [(LayerId, LayerOffset)],
ambient_is_root: bool,
) -> Self {
Self {
inputs: Inputs {
stack,
ctx,
cached_indices,
},
site: Site {
ambient,
is_root: ambient_is_root,
path: Path::abs_root(),
},
frame: None,
frame_depth: 0,
output: PrimIndexGraph::default(),
tasks: Vec::new(),
implied_seen: HashSet::new(),
root_contributes: true,
errors: Vec::new(),
pending_relocation_errors: Vec::new(),
}
}
pub(crate) fn build(mut self, path: &Path) -> BuildResult<BuildOutput> {
if self.frame_depth > MAX_DEPTH {
return Ok(BuildOutput {
graph: None,
errors: self.errors,
});
}
self.site.path = path.clone();
if !self.seed(path)? {
return Ok(BuildOutput {
graph: None,
errors: self.errors,
});
}
if !self.root_contributes || self.inputs.ctx.within_instance() {
let local_root = self.output.local_root();
if local_root.is_valid() {
self.output.nodes[local_root.idx()].flags |= NodeFlags::INERT;
if !self.root_contributes {
self.output.nodes[local_root.idx()].restriction_depth = path.element_count() as u16;
}
}
}
if self.elide_if_prohibited() {
self.output.finalize_strength_order();
return Ok(BuildOutput {
graph: Some(self.output),
errors: self.errors,
});
}
self.scan_and_enqueue();
while let Some(task) = self.take_best_task() {
match task.kind {
TaskKind::EvalNodeRelocations => self.eval_node_relocations(task.node)?,
TaskKind::EvalImpliedRelocations => self.eval_implied_relocations(task.node)?,
TaskKind::EvalNodeReferences | TaskKind::EvalNodePayloads => self.eval_arcs(task.node, task.kind)?,
TaskKind::EvalNodeInherits => {
self.eval_class_arcs(task.node, FieldKey::InheritPaths, ArcType::Inherit)?
}
TaskKind::EvalNodeSpecializes => {
self.eval_class_arcs(task.node, FieldKey::Specializes, ArcType::Specialize)?
}
TaskKind::EvalImpliedClasses => {
self.implied_seen.remove(&(task.node, TaskKind::EvalImpliedClasses));
self.eval_implied_classes(task.node)?;
}
TaskKind::EvalImpliedSpecializes => {
self.implied_seen.remove(&(task.node, TaskKind::EvalImpliedSpecializes));
self.eval_implied_specializes(task.node)?;
}
TaskKind::EvalNodeVariantSets => self.eval_node_variant_sets(task.node)?,
TaskKind::EvalNodeAncestralVariantSets => self.eval_node_ancestral_variant_sets(task.node)?,
TaskKind::EvalNodeVariantAuthored => {
self.eval_node_authored_variant(task.node, &task.variant, false)?
}
TaskKind::EvalNodeAncestralVariantAuthored => {
self.eval_node_authored_variant(task.node, &task.variant, true)?
}
TaskKind::EvalNodeVariantFallback => {
self.eval_node_fallback_variant(task.node, &task.variant, false)?
}
TaskKind::EvalNodeAncestralVariantFallback => {
self.eval_node_fallback_variant(task.node, &task.variant, true)?
}
TaskKind::EvalNodeVariantNoneFound | TaskKind::EvalNodeAncestralVariantNoneFound => {}
}
}
for (node, error) in std::mem::take(&mut self.pending_relocation_errors) {
if !self.output.nodes[node.idx()].is_culled() {
self.errors.push(error);
}
}
self.output.finalize_strength_order();
Ok(BuildOutput {
graph: Some(self.output),
errors: self.errors,
})
}
fn root_layer_id(&self) -> LayerId {
self.site.ambient.first().map(|&(id, _)| id).unwrap_or(LayerId::INVALID)
}
fn seed(&mut self, path: &Path) -> BuildResult<bool> {
let parent = path.parent();
let needs_ancestor = matches!(&parent, Some(p) if p != &Path::abs_root());
if !needs_ancestor {
self.output.init_root(self.root_layer_id(), path.clone());
self.add_local_root(path);
return Ok(true);
}
let parent = parent.expect("checked by needs_ancestor");
let graph = if self.frame.is_none() && self.site.is_root {
let Some(parent_index) = self.inputs.cached_indices.get(&parent) else {
return Ok(false);
};
parent_index.graph().clone()
} else {
let Some(graph) = self.merge_subindex(self.compose_ancestral_subindex(&parent)?) else {
return Ok(false);
};
graph
};
if graph.nodes.iter().any(|n| {
!n.is_inert()
&& !n.is_culled()
&& !matches!(
n.arc,
ArcType::Root
| ArcType::Reference
| ArcType::Payload
| ArcType::Inherit
| ArcType::Specialize
| ArcType::Variant
| ArcType::Relocate
)
}) {
return Ok(false);
}
self.output = graph;
self.output.append_child_name_to_all_sites(path);
if !self.output.local_root().is_valid() {
self.add_local_root(path);
}
Ok(true)
}
fn add_local_root(&mut self, path: &Path) {
self.output.add_site_child(
NodeId::INVALID,
self.site.ambient.to_vec(),
path.clone(),
ArcType::Root,
MapFunction::identity(),
false,
);
}
fn frame_skip(&self) -> bool {
self.frame.is_some_and(|f| f.skip)
}
fn new_sub<'b, 'g>(
&self,
ambient: &'b [(LayerId, LayerOffset)],
ambient_is_root: bool,
frame: Option<&'g Frame<'g>>,
root_contributes: bool,
) -> Indexer<'b, 'g>
where
'a: 'b,
{
let mut sub = Indexer::new(
self.inputs.stack,
self.inputs.ctx,
self.inputs.cached_indices,
ambient,
ambient_is_root,
);
sub.frame = frame;
sub.frame_depth = self.frame_depth + 1;
sub.root_contributes = root_contributes;
sub
}
fn compose_ancestral_subindex(&self, parent: &Path) -> BuildResult<BuildOutput> {
self.new_sub(self.site.ambient, self.site.is_root, self.frame, true)
.build(parent)
}
fn compose_subindex(
&self,
target: &Path,
ambient: &[(LayerId, LayerOffset)],
ambient_is_root: bool,
skip: bool,
root_contributes: bool,
arc: ArcType,
) -> BuildResult<BuildOutput> {
let frame = Frame {
requested_path: target.clone(),
root_path: self.site.path.clone(),
graph: &self.output,
arc,
requested_layer: ambient[0].0,
skip,
previous: self.frame,
};
self.new_sub(ambient, ambient_is_root, Some(&frame), root_contributes)
.build(target)
}
fn merge_subindex(&mut self, out: BuildOutput) -> Option<PrimIndexGraph> {
self.errors.extend(out.errors);
out.graph
}
#[allow(clippy::too_many_arguments)]
fn compose_and_graft(
&mut self,
target: &Path,
ambient: &[(LayerId, LayerOffset)],
ambient_is_root: bool,
skip: bool,
parent: NodeId,
arc: ArcType,
map: MapFunction,
origin: NodeId,
sibling: u16,
) -> BuildResult<Option<NodeId>> {
let Some(sub) =
self.merge_subindex(self.compose_subindex(target, ambient, ambient_is_root, skip, true, arc)?)
else {
return Ok(None);
};
let Some(grafted) = self.graft_subindex(&sub, parent, arc, map, origin, sibling) else {
return Ok(None);
};
self.add_grafted_subtree_tasks(grafted);
Ok(Some(grafted))
}
fn find_duplicate(&self, rep_layer: LayerId, site_path: &Path) -> bool {
if self.output.node_using_site(rep_layer, site_path).is_some() {
return true;
}
self.frame_sites(site_path)
.any(|(f, search)| f.graph.node_using_site(rep_layer, &search).is_some())
}
fn frame_sites(&self, target: &Path) -> FrameSites<'f> {
FrameSites {
search: target.clone(),
current_root: self.site.path.clone(),
frame: self.frame,
}
}
fn graft_subindex(
&mut self,
source: &PrimIndexGraph,
parent: NodeId,
arc: ArcType,
root_map: MapFunction,
origin: NodeId,
sibling: u16,
) -> Option<NodeId> {
let local_root = source.local_root();
if !local_root.is_valid() {
return None;
}
let synthetic = source.root;
let extra_roots = source[synthetic.idx()]
.children()
.iter()
.any(|&c| c != local_root && !source[c.idx()].is_inert());
if extra_roots {
return None;
}
let weak = arc == ArcType::Specialize;
let parent_depth = self.node(parent).path.prim_element_count() as u16;
let mut remap: Vec<Option<NodeId>> = vec![None; source.nodes.len()];
let mut grafted_root = NodeId::INVALID;
for sid in 0..source.nodes.len() {
if sid == synthetic.idx() {
continue;
}
let node = &source.nodes[sid];
let (struct_parent, node_map, node_arc, node_sibling, node_depth, node_origin) = if sid == local_root.idx()
{
(parent, root_map.clone(), arc, sibling, parent_depth, Some(origin))
} else {
let grafted_parent = node.parent().and_then(|p| remap[p.idx()]).unwrap_or(parent);
let grafted_origin = node.origin().and_then(|o| remap[o.idx()]).or(Some(grafted_parent));
(
grafted_parent,
node.map_to_parent.clone(),
node.arc,
node.sibling_num_at_origin,
node.namespace_depth,
grafted_origin,
)
};
let new_id = self.output.add_site_child(
struct_parent,
node.layer_stack().to_vec(),
node.path.clone(),
node_arc,
node_map,
false,
);
let n = &mut self.output.nodes[new_id.idx()];
n.has_specs = node.has_specs;
n.flags = node.flags;
n.restriction_depth = node.restriction_depth;
if weak {
n.flags |= NodeFlags::HAS_SPECIALIZES;
}
n.sibling_num_at_origin = node_sibling;
n.namespace_depth = node_depth;
n.origin = node_origin;
remap[sid] = Some(new_id);
if sid == local_root.idx() {
grafted_root = new_id;
}
}
Some(grafted_root)
}
fn scan_and_enqueue(&mut self) {
for i in 0..self.output.nodes.len() {
if self.output.nodes[i].is_inert() {
continue;
}
let node = NodeId(i as u32);
let has_specs = self.stack_has_spec(self.output.nodes[i].layer_stack(), &self.output.nodes[i].path);
self.output.nodes[i].has_specs = has_specs;
if has_specs {
self.add_expressed_arc_tasks(node);
}
self.enqueue_relocations(node);
}
}
fn add_expressed_arc_tasks(&mut self, node: NodeId) {
self.tasks.push(Task::new(TaskKind::EvalNodeReferences, node));
if self.inputs.stack.load_payloads() {
self.tasks.push(Task::new(TaskKind::EvalNodePayloads, node));
}
self.tasks.push(Task::new(TaskKind::EvalNodeInherits, node));
self.tasks.push(Task::new(TaskKind::EvalNodeSpecializes, node));
if self.frame.is_none() {
self.tasks.push(Task::new(TaskKind::EvalNodeVariantSets, node));
}
}
fn add_tasks_for_node(&mut self, node: NodeId) {
self.add_implied_tasks_for_node(node);
self.add_expressed_arc_tasks(node);
self.enqueue_relocations(node);
}
fn add_implied_tasks_for_node(&mut self, node: NodeId) {
if is_class_based_arc(self.node(node).arc) {
let start = self.find_starting_node_for_implied_classes(node);
self.enqueue_implied(start);
} else if self.has_class_based_child(node) {
self.enqueue_implied(node);
}
if self.frame.is_none() && self.has_specialize_in_subtree(node) {
self.enqueue_implied_specializes(node);
}
}
fn add_grafted_subtree_tasks(&mut self, root: NodeId) {
self.add_implied_tasks_for_node(root);
for id in self.subtree_nodes(root) {
self.enqueue_relocations(id);
}
if self.frame.is_some() {
return;
}
for id in self.subtree_nodes(root) {
self.tasks.push(Task::new(TaskKind::EvalNodeVariantSets, id));
self.tasks.push(Task::new(TaskKind::EvalNodeAncestralVariantSets, id));
}
}
fn take_best_task(&mut self) -> Option<Task> {
let best = (0..self.tasks.len()).max_by(|&i, &j| self.task_priority_cmp(&self.tasks[i], &self.tasks[j]))?;
Some(self.tasks.swap_remove(best))
}
fn task_priority_cmp(&self, a: &Task, b: &Task) -> Ordering {
if a.kind != b.kind {
return a.kind.cmp(&b.kind);
}
match a.kind {
TaskKind::EvalNodeVariantAuthored
| TaskKind::EvalNodeVariantFallback
| TaskKind::EvalNodeAncestralVariantAuthored
| TaskKind::EvalNodeAncestralVariantFallback => {
if a.node != b.node {
return self.output.compare_node_strength(b.node, a.node);
}
Self::variant_key(b).cmp(&Self::variant_key(a))
}
TaskKind::EvalNodeVariantNoneFound | TaskKind::EvalNodeAncestralVariantNoneFound => {
(b.node, Self::variant_key(b)).cmp(&(a.node, Self::variant_key(a)))
}
TaskKind::EvalImpliedClasses => {
if b.node > a.node && self.output.is_ancestor_of(a.node, b.node) {
return Ordering::Less;
}
if a.node > b.node && self.output.is_ancestor_of(b.node, a.node) {
return Ordering::Greater;
}
self.output.compare_node_strength(b.node, a.node)
}
_ => a.node.cmp(&b.node),
}
}
fn variant_key(task: &Task) -> (&str, u16) {
let v = task.variant.as_ref().expect("variant task carries a VariantTask");
(v.vset_path.as_str(), v.vset_num)
}
fn enqueue_implied(&mut self, node: NodeId) {
if self.implied_seen.insert((node, TaskKind::EvalImpliedClasses)) {
self.tasks.push(Task::new(TaskKind::EvalImpliedClasses, node));
}
}
fn enqueue_implied_specializes(&mut self, node: NodeId) {
if self.implied_seen.insert((node, TaskKind::EvalImpliedSpecializes)) {
self.tasks.push(Task::new(TaskKind::EvalImpliedSpecializes, node));
}
}
fn enqueue_relocations(&mut self, node: NodeId) {
if self.inputs.stack.has_relocates() && !self.node(node).is_inert() {
self.tasks.push(Task::new(TaskKind::EvalNodeRelocations, node));
}
}
fn eval_node_relocations(&mut self, node: NodeId) -> BuildResult<()> {
{
let n = self.node(node);
if n.is_inert() || n.is_culled() || n.is_permission_denied() {
return Ok(());
}
}
let already_relocated = self
.node(node)
.children()
.iter()
.any(|&c| self.node(c).arc == ArcType::Relocate);
let node_ambient = self.node(node).layer_stack().to_vec();
let node_path = self.node(node).path.clone();
let Some(source) = self.inputs.stack.relocation_source(&node_ambient, &node_path) else {
return Ok(());
};
let opinion_layer = if already_relocated {
None
} else {
node_ambient
.iter()
.find(|&&(li, _)| self.inputs.stack.layer(li).has_spec(&source))
.map(|&(li, _)| self.inputs.stack.layer(li).identifier.clone())
};
for child in self.node(node).children().to_vec() {
if self.node(child).arc != ArcType::Variant {
self.elide_subtree(child);
}
}
let ambient_is_root = self.ambient_is_root_for(&node_ambient);
let Some(sub) = self.merge_subindex(self.compose_subindex(
&source,
&node_ambient,
ambient_is_root,
self.frame_skip(),
false,
ArcType::Relocate,
)?) else {
return Ok(());
};
let source_depth = source.prim_element_count();
if let Some((reloc_source, reloc_layer)) = sub.nodes.iter().find_map(|n| {
if n.path != source
&& matches!(n.arc, ArcType::Reference | ArcType::Payload)
&& n.path.prim_element_count() == source_depth
{
self.inputs
.stack
.relocation_source_layer(n.layer_stack(), &n.path)
.map(|l| (n.path.clone(), l.to_string()))
} else {
None
}
}) {
let site_layer = self.introducing_layer(node);
self.errors.push(Error::ProhibitedRelocationSource {
arc: ArcType::Relocate,
site: node_path.clone(),
site_layer: site_layer.clone(),
target: source.clone(),
target_layer: site_layer,
reloc_source,
reloc_layer,
composing: self.site.path.clone(),
});
return Ok(());
}
let map = MapFunction::from_pair_identity(source.clone(), node_path);
let Some(grafted) = self.graft_subindex(&sub, node, ArcType::Relocate, map, node, 0) else {
return Ok(());
};
self.output.nodes[grafted.idx()].flags |= NodeFlags::RELOCATE_SOURCE;
if let Some(layer) = opinion_layer {
self.pending_relocation_errors.push((
grafted,
Error::OpinionAtRelocationSource {
source_path: source.clone(),
layer,
composing: self.site.path.clone(),
},
));
}
self.add_grafted_subtree_tasks(grafted);
self.tasks.push(Task::new(TaskKind::EvalImpliedRelocations, grafted));
self.elide_relocated_subtrees(grafted);
Ok(())
}
fn eval_implied_relocations(&mut self, _node: NodeId) -> BuildResult<()> {
Ok(())
}
fn node_is_relocation_source(&self, node: NodeId) -> bool {
let n = self.node(node);
if n.is_inert() || n.is_culled() {
return false;
}
self.inputs.stack.is_relocation_source(n.layer_stack(), &n.path)
}
fn elide_subtree(&mut self, root: NodeId) {
for id in self.subtree_nodes(root) {
let n = &mut self.output.nodes[id.idx()];
n.flags |= NodeFlags::CULLED;
if n.restriction_depth == 0 {
n.restriction_depth = 1;
}
}
}
fn elide_relocated_subtrees(&mut self, root: NodeId) {
if !self.inputs.stack.has_relocates() {
return;
}
let mut stack: Vec<NodeId> = self.node(root).children().to_vec();
while let Some(id) = stack.pop() {
if self.node(id).arc == ArcType::Relocate {
continue;
}
if self.node_is_relocation_source(id) {
self.elide_subtree(id);
continue;
}
stack.extend(self.node(id).children().iter().copied());
}
}
fn elide_if_prohibited(&mut self) -> bool {
if !self.inputs.stack.has_relocates() {
return false;
}
let prohibited = (0..self.output.nodes.len()).any(|i| self.node_is_relocation_source(NodeId(i as u32)));
if !prohibited {
return false;
}
let root = self.output.root;
for (i, n) in self.output.nodes.iter_mut().enumerate() {
if NodeId(i as u32) == root {
n.flags |= NodeFlags::INERT;
} else {
n.flags |= NodeFlags::CULLED;
}
}
true
}
fn eval_arcs(&mut self, node: NodeId, kind: TaskKind) -> BuildResult<()> {
if self.node(node).is_inert() {
return Ok(());
}
let expr_vars = self.composed_expr_vars(node);
let site_path = self.node(node).path.clone();
let mut arc_errors: Vec<Error> = Vec::new();
let (arc, arcs) = match kind {
TaskKind::EvalNodeReferences => {
let refs = compose_references_in(
self.node_slice(node),
self.inputs.stack,
self.inputs.stack.resolver(),
&expr_vars,
&site_path,
&mut arc_errors,
)?;
let arcs = refs
.into_iter()
.map(|r| (r.asset_path, r.prim_path, r.layer_offset.sanitized()))
.collect::<Vec<_>>();
(ArcType::Reference, arcs)
}
TaskKind::EvalNodePayloads => {
let payloads = collect_payloads_in(
self.node_slice(node),
self.inputs.stack,
self.inputs.stack.resolver(),
&expr_vars,
&site_path,
&mut arc_errors,
)?;
let arcs = payloads
.into_iter()
.map(|p| {
(
p.asset_path,
p.prim_path,
p.layer_offset.unwrap_or_default().sanitized(),
)
})
.collect::<Vec<_>>();
(ArcType::Payload, arcs)
}
_ => unreachable!("eval_arcs handles only reference and payload tasks"),
};
self.errors.append(&mut arc_errors);
for (asset_path, prim_path, offset) in &arcs {
self.add_ref_or_payload_arc(node, asset_path, prim_path, arc, *offset)?;
}
Ok(())
}
fn composed_expr_vars(&self, node: NodeId) -> HashMap<String, Value> {
let mut composed = HashMap::new();
let mut cur = Some(node);
while let Some(id) = cur {
let n = &self.output.nodes[id.idx()];
if matches!(n.arc, ArcType::Root | ArcType::Reference | ArcType::Payload) {
composed.extend(self.layer_stack_expr_vars(n.layer_stack()));
}
cur = n.parent;
}
composed
}
fn layer_stack_expr_vars(&self, members: &[(LayerId, LayerOffset)]) -> HashMap<String, Value> {
let mut vars = HashMap::new();
for &(layer, _) in members.iter().rev() {
if let Ok(dict) = expr::read_expression_variables(self.inputs.stack.layer(layer).data()) {
vars.extend(dict);
}
}
vars
}
fn eval_class_arcs(&mut self, node: NodeId, field: FieldKey, arc: ArcType) -> BuildResult<()> {
if self.node(node).is_inert() {
return Ok(());
}
let arcs = compose_arc_list_in::<Path>(self.node_slice(node), field, self.inputs.stack)?;
let node_path = self.node(node).path.clone();
let node_ambient = self.node(node).layer_stack().to_vec();
for (arc_num, class_path) in arcs.iter().enumerate() {
let resolved = node_path.make_absolute(class_path);
if resolved.is_prim_variant_selection_path() {
continue;
}
if let Some((reloc_source, reloc_layer)) = self.prohibiting_relocation_source(&node_ambient, &resolved) {
let site_layer = self.introducing_layer(node);
self.errors.push(Error::ProhibitedRelocationSource {
arc,
site: node_path.clone(),
site_layer: site_layer.clone(),
target: resolved.clone(),
target_layer: site_layer,
reloc_source,
reloc_layer,
composing: self.site.path.clone(),
});
continue;
}
let class_map = self
.fold_relocates_into(
MapFunction::from_pair(resolved, node_path.strip_all_variant_selections()),
node,
)
.with_root_identity();
self.add_class_based_arc(node, node, class_map, arc_num as u16, None, arc, false)?;
}
Ok(())
}
fn eval_node_variant_sets(&mut self, node: NodeId) -> BuildResult<()> {
if !self.node_can_contribute_specs(node) {
return Ok(());
}
let site_path = self.node(node).path.clone();
self.eval_variant_sets_at_site(node, &site_path, false)
}
fn eval_node_ancestral_variant_sets(&mut self, node: NodeId) -> BuildResult<()> {
let mut path = self.node(node).path.parent();
while let Some(p) = path {
if p == Path::abs_root() {
break;
}
if self.node_can_contribute_ancestral(node, &p) {
self.eval_variant_sets_at_site(node, &p, true)?;
}
if p.is_prim_variant_selection_path() {
break;
}
path = p.parent();
}
Ok(())
}
fn eval_variant_sets_at_site(&mut self, node: NodeId, site_path: &Path, is_ancestral: bool) -> BuildResult<()> {
let sets = self.compose_token_children(node, site_path, ChildrenKey::VariantSetChildren)?;
let kind = if is_ancestral {
TaskKind::EvalNodeAncestralVariantAuthored
} else {
TaskKind::EvalNodeVariantAuthored
};
for (vset_num, vset_name) in sets.into_iter().enumerate() {
self.tasks.push(Task::variant(
kind,
node,
VariantTask {
vset_path: site_path.clone(),
vset_name,
vset_num: vset_num as u16,
},
));
}
Ok(())
}
fn eval_node_authored_variant(
&mut self,
node: NodeId,
vt: &Option<VariantTask>,
is_ancestral: bool,
) -> BuildResult<()> {
let Some(vt) = vt else { return Ok(()) };
if !self.node_can_contribute_variant(node, &vt.vset_path, is_ancestral) {
return Ok(());
}
match self.compose_variant_selection(node, &vt.vset_path, &vt.vset_name)? {
Some(sel) if !sel.is_empty() => self.add_variant_arc(node, vt, &sel, is_ancestral)?,
_ => {
let kind = if is_ancestral {
TaskKind::EvalNodeAncestralVariantFallback
} else {
TaskKind::EvalNodeVariantFallback
};
self.tasks.push(Task::variant(kind, node, vt.clone()));
}
}
Ok(())
}
fn eval_node_fallback_variant(
&mut self,
node: NodeId,
vt: &Option<VariantTask>,
is_ancestral: bool,
) -> BuildResult<()> {
let Some(vt) = vt else { return Ok(()) };
if !self.node_can_contribute_variant(node, &vt.vset_path, is_ancestral) {
return Ok(());
}
let options = self.compose_variant_options(node, &vt.vset_path, &vt.vset_name)?;
let fallback = self
.inputs
.ctx
.variant_fallbacks
.get(&vt.vset_name)
.iter()
.find(|fb| options.iter().any(|o| o == *fb))
.cloned();
match fallback {
Some(sel) => self.add_variant_arc(node, vt, &sel, is_ancestral)?,
None => {
let kind = if is_ancestral {
TaskKind::EvalNodeAncestralVariantNoneFound
} else {
TaskKind::EvalNodeVariantNoneFound
};
self.tasks.push(Task::variant(kind, node, vt.clone()));
}
}
Ok(())
}
fn compose_variant_selection(&self, origin: NodeId, vset_path: &Path, vset: &str) -> BuildResult<Option<String>> {
let (start, path_in_start) = self.translate_path_to_root_or_closest(origin, vset_path);
let mut nodes: Vec<NodeId> = self
.subtree_nodes(start)
.into_iter()
.filter(|&n| !self.node(n).is_inert())
.collect();
nodes.sort_by(|&a, &b| self.output.compare_node_strength(a, b));
for n in nodes {
let node = self.node(n);
let Some(site) = self.map_path_down(start, n, &path_in_start) else {
continue;
};
let path_in_node = site.strip_all_variant_selections();
let site = if node.arc == ArcType::Variant {
variant_storage_site(&node.path, &path_in_node).unwrap_or(path_in_node)
} else {
path_in_node
};
for &(layer, _) in node.layer_stack() {
let Some(value) = self
.inputs
.stack
.layer(layer)
.try_get(&site, FieldKey::VariantSelection.as_str())?
else {
continue;
};
if let Value::VariantSelectionMap(map) = value.into_owned() {
if let Some(sel) = map.get(vset) {
return Ok(Some(sel.clone()));
}
}
}
}
Ok(None)
}
fn translate_path_to_root_or_closest(&self, node: NodeId, path: &Path) -> (NodeId, Path) {
let local_root = self.output.local_root();
let mut cur_path = path.strip_all_variant_selections();
let mut cur_node = node;
while cur_node != local_root && cur_node != self.output.root {
let Some(in_parent) = self.node(cur_node).map_to_parent.map_source_to_target(&cur_path) else {
break;
};
let Some(parent) = self.node(cur_node).parent() else {
break;
};
cur_node = parent;
cur_path = in_parent;
}
let intro = self.node(cur_node).path_at_introduction();
let stripped = intro.strip_all_variant_selections();
if intro != stripped {
if let Some(p) = cur_path.replace_prefix(&stripped, &intro) {
cur_path = p;
}
}
(cur_node, cur_path)
}
fn map_path_down(&self, ancestor: NodeId, descendant: NodeId, path: &Path) -> Option<Path> {
let mut chain = Vec::new();
let mut cur = descendant;
while cur != ancestor {
chain.push(cur);
cur = self.node(cur).parent()?;
}
let mut p = path.clone();
for &node in chain.iter().rev() {
p = self.node(node).map_to_parent.map_target_to_source(&p)?;
}
Some(p)
}
fn subtree_nodes(&self, root: NodeId) -> Vec<NodeId> {
let mut out = Vec::new();
let mut stack = vec![root];
while let Some(id) = stack.pop() {
out.push(id);
stack.extend(self.node(id).children().iter().copied());
}
out
}
fn compose_variant_options(&self, node: NodeId, vset_path: &Path, vset: &str) -> BuildResult<Vec<String>> {
let set_path = vset_path.append_variant_selection(vset, "");
self.compose_token_children(node, &set_path, ChildrenKey::VariantChildren)
}
fn add_variant_arc(&mut self, node: NodeId, vt: &VariantTask, vsel: &str, is_ancestral: bool) -> BuildResult<()> {
if is_ancestral {
return self.add_ancestral_variant_arc(node, vt, vsel);
}
let n = self.node(node);
let base = n.path.clone();
let layers = n.layer_stack().to_vec();
let var_path = base.append_variant_selection(&vt.vset_name, vsel);
let has_specs = self.stack_has_spec(&layers, &var_path);
let map = MapFunction::from_pair_identity(var_path.clone(), base);
let new_node = self
.output
.add_site_child(node, layers, var_path, ArcType::Variant, map, false);
let n = &mut self.output.nodes[new_node.idx()];
n.sibling_num_at_origin = vt.vset_num;
n.has_specs = has_specs;
self.add_tasks_for_node(new_node);
self.retry_variant_tasks();
Ok(())
}
fn add_ancestral_variant_arc(&mut self, node: NodeId, vt: &VariantTask, vsel: &str) -> BuildResult<()> {
let n = self.node(node);
let node_path = n.path.clone();
let layers = n.layer_stack().to_vec();
let ambient_is_root = self.ambient_is_root_for(&layers);
let selected = vt.vset_path.append_variant_selection(&vt.vset_name, vsel);
let Some(var_path) = node_path.replace_prefix(&vt.vset_path, &selected) else {
return Ok(());
};
let map = MapFunction::from_pair_identity(var_path.clone(), node_path);
let skip = self.variant_arc_skips_duplicates(node);
if skip && self.find_duplicate(layers[0].0, &var_path) {
return Ok(());
}
let grafted = self.compose_and_graft(
&var_path,
&layers,
ambient_is_root,
skip,
node,
ArcType::Variant,
map,
node,
vt.vset_num,
)?;
if grafted.is_some() {
self.retry_variant_tasks();
}
Ok(())
}
fn variant_arc_skips_duplicates(&self, node: NodeId) -> bool {
let mut cur = node;
loop {
let n = self.node(cur);
if is_class_based_arc(n.arc) && n.depth_below_introduction() == 0 && !n.is_inert() {
return true;
}
match n.parent() {
Some(p) if p != self.output.root => cur = p,
_ => return false,
}
}
}
fn retry_variant_tasks(&mut self) {
for task in &mut self.tasks {
task.kind = match task.kind {
TaskKind::EvalNodeVariantFallback | TaskKind::EvalNodeVariantNoneFound => {
TaskKind::EvalNodeVariantAuthored
}
TaskKind::EvalNodeAncestralVariantFallback | TaskKind::EvalNodeAncestralVariantNoneFound => {
TaskKind::EvalNodeAncestralVariantAuthored
}
other => other,
};
}
}
#[allow(clippy::too_many_arguments)]
fn add_class_based_arc(
&mut self,
parent: NodeId,
origin: NodeId,
inherit_map: MapFunction,
arc_num: u16,
ignore_if_same_as_site: Option<(LayerId, Path)>,
arc: ArcType,
implied: bool,
) -> BuildResult<Option<NodeId>> {
let parent_path = self.node(parent).path.clone();
let Some(inherit_path) = determine_inherit_path(&parent_path, &inherit_map) else {
return Ok(None);
};
let parent_layers = self.node(parent).layer_stack().to_vec();
let rep = parent_layers[0].0;
if !implied && self.class_arc_is_cycle(parent, rep, &inherit_path) {
self.errors
.push(Error::ArcCycle(self.cycle_error(parent, arc, rep, inherit_path)));
return Ok(None);
}
if let Some(existing) = self.find_matching_child(parent, rep, &inherit_path, arc, &inherit_map, origin) {
return Ok(Some(existing));
}
if arc == ArcType::Specialize {
let add_placeholder = parent != self.output.local_root() || self.frame.is_some();
if add_placeholder {
let has_specs = self.stack_has_spec(&parent_layers, &inherit_path);
let placeholder =
self.output
.add_site_child(parent, parent_layers, inherit_path, arc, inherit_map, true);
{
let n = &mut self.output.nodes[placeholder.idx()];
n.origin = Some(origin);
n.sibling_num_at_origin = arc_num;
n.has_specs = has_specs;
n.flags |= NodeFlags::INERT;
if implied {
n.flags |= NodeFlags::IMPLIED_CLASS;
}
}
if implied {
self.add_implied_tasks_for_node(placeholder);
}
if self.frame.is_none() && !self.is_relocates_placeholder_implied_arc(placeholder) {
let propagated = self.propagate_node_to_root(placeholder)?;
if matches!(propagated, Some(p) if self.node(p).origin() != Some(placeholder)) {
self.enqueue_implied(placeholder);
}
return Ok(propagated);
}
return Ok(Some(placeholder));
}
}
let same_as_ignore = ignore_if_same_as_site
.as_ref()
.is_some_and(|(li, p)| *li == rep && *p == inherit_path);
let direct_should = inherit_path != parent_path && !same_as_ignore;
let skip = direct_should || self.frame_skip();
if skip && self.find_duplicate(rep, &inherit_path) {
return Ok(None);
}
if direct_should && !inherit_path.is_root_prim() {
let target_is_root = self.ambient_is_root_for(&parent_layers);
let grafted = self.compose_and_graft(
&inherit_path,
&parent_layers,
target_is_root,
skip,
parent,
arc,
inherit_map,
origin,
arc_num,
)?;
if implied {
if let Some(g) = grafted {
self.output.nodes[g.idx()].flags |= NodeFlags::IMPLIED_CLASS;
}
}
return Ok(grafted);
}
let has_specs = self.stack_has_spec(&parent_layers, &inherit_path);
let new_node = self.output.add_site_child(
parent,
parent_layers,
inherit_path,
arc,
inherit_map,
arc == ArcType::Specialize,
);
let n = &mut self.output.nodes[new_node.idx()];
n.origin = Some(origin);
n.sibling_num_at_origin = arc_num;
n.has_specs = has_specs;
if implied {
n.flags |= NodeFlags::IMPLIED_CLASS;
}
if !direct_should {
n.flags |= NodeFlags::INERT;
}
self.add_tasks_for_node(new_node);
Ok(Some(new_node))
}
fn eval_implied_classes(&mut self, node: NodeId) -> BuildResult<()> {
let Some(parent) = self.node(node).parent() else {
return Ok(());
};
if parent == self.output.root {
return Ok(());
}
if !self.has_class_based_child(node) {
return Ok(());
}
let transfer = self.node(node).map_to_parent.with_root_identity();
self.eval_implied_class_tree(parent, node, &transfer, true)
}
fn eval_implied_class_tree(
&mut self,
dest: NodeId,
src: NodeId,
transfer: &MapFunction,
start_of_tree: bool,
) -> BuildResult<()> {
if self.node(dest).arc == ArcType::Relocate {
if let Some(dest_parent) = self.node(dest).parent() {
let new_transfer = self
.node(dest)
.map_to_parent
.clone()
.with_root_identity()
.compose(transfer);
self.eval_implied_class_tree(dest_parent, src, &new_transfer, start_of_tree)?;
}
self.enqueue_implied(dest);
return Ok(());
}
let src_is_class = is_class_based_arc(self.node(src).arc);
let src_depth = self.node(src).depth_below_introduction();
let crosses_subroot_ref =
matches!(self.node(src).arc, ArcType::Reference | ArcType::Payload) && !self.node(src).path.is_root_prim();
let src_owner = self.output.get_propagated_specializes_node(src).unwrap_or(src);
let src_children = self.node(src_owner).children().to_vec();
for child in src_children {
let c = self.node(child);
if !is_class_based_arc(c.arc) {
continue;
}
if start_of_tree && src_is_class && src_depth == c.depth_below_introduction() {
continue;
}
let child_arc = c.arc;
let child_map = if crosses_subroot_ref {
c.map_to_parent.deepen_to(&c.path)
} else {
c.map_to_parent.clone()
};
let child_site = (c.layer_id(), c.path.clone());
let sibling = c.sibling_num_at_origin;
let dest_class_func = if self.node(src).arc == ArcType::Relocate {
transfer.compose(&child_map).dropping_source(&self.node(src).path)
} else {
transfer.implied_class(&child_map)
};
let dest_child = match self.find_implied_child(dest, child) {
Some(existing) => Some(existing),
None => self.add_class_based_arc(
dest,
child,
dest_class_func.clone(),
sibling,
Some(child_site),
child_arc,
true,
)?,
};
if let Some(dest_child) = dest_child {
if self.has_class_based_child(child) {
let child_transfer = dest_class_func.inverse().compose(&transfer.compose(&child_map));
let recurse_into = self
.output
.get_propagated_specializes_node(dest_child)
.unwrap_or(dest_child);
self.eval_implied_class_tree(recurse_into, child, &child_transfer, false)?;
}
}
}
Ok(())
}
fn find_starting_node_for_implied_classes(&self, n: NodeId) -> NodeId {
let mut start = n;
while is_class_based_arc(self.node(start).arc) {
let (instance, class) = self.output.starting_node_of_class_hierarchy(start);
start = instance;
if is_class_based_arc(self.node(instance).arc) {
let ancestral = self.node(instance).path_at_introduction();
if self.node(class).path.has_prefix(&ancestral) {
break;
}
}
}
start
}
fn find_matching_child(
&self,
parent: NodeId,
rep_layer: LayerId,
path: &Path,
arc: ArcType,
map: &MapFunction,
origin: NodeId,
) -> Option<NodeId> {
let parent_is_relocate = self.node(parent).arc == ArcType::Relocate
|| (!self.root_contributes && parent == self.output.local_root());
let origin_depth = self.node(origin).depth_below_introduction();
self.node(parent).children().iter().copied().find(|&c| {
let cn = self.node(c);
if parent_is_relocate {
cn.arc == arc
&& cn.map_to_parent == *map
&& cn.origin().map(|o| self.node(o).depth_below_introduction()) == Some(origin_depth)
} else {
cn.layer_id() == rep_layer && &cn.path == path
}
})
}
fn find_implied_child(&self, dest: NodeId, src_child: NodeId) -> Option<NodeId> {
self.node(dest)
.children()
.iter()
.copied()
.find(|&c| self.node(c).origin() == Some(src_child) && !self.output.is_propagated_specializes(c))
}
fn has_class_based_child(&self, node: NodeId) -> bool {
let target = self.output.get_propagated_specializes_node(node).unwrap_or(node);
self.node(target)
.children()
.iter()
.any(|&c| is_class_based_arc(self.node(c).arc))
}
fn has_specialize_in_subtree(&self, node: NodeId) -> bool {
let mut stack = vec![node];
while let Some(id) = stack.pop() {
if self.node(id).arc == ArcType::Specialize {
return true;
}
stack.extend(self.node(id).children().iter().copied());
}
false
}
fn is_relocates_placeholder_implied_arc(&self, node: NodeId) -> bool {
let n = self.node(node);
let Some(parent) = n.parent() else {
return false;
};
n.origin() != Some(parent)
&& self.node(parent).arc == ArcType::Relocate
&& self.node(parent).layer_id() == n.layer_id()
&& self.node(parent).path == n.path
}
fn propagate_node_to_root(&mut self, src: NodeId) -> BuildResult<Option<NodeId>> {
let root = self.output.local_root();
if !root.is_valid() {
return Ok(None);
}
let map = self.node(src).map_to_root.clone();
let src_layers = self.node(src).layer_stack().to_vec();
let rep = src_layers[0].0;
let src_path = self.node(src).path.clone();
let sibling = self.node(src).sibling_num_at_origin;
if let Some(existing) = self.find_matching_child(root, rep, &src_path, ArcType::Specialize, &map, src) {
return Ok(Some(existing));
}
if self.find_duplicate(rep, &src_path) {
return Ok(None);
}
if !src_path.is_root_prim() {
let target_is_root = self.ambient_is_root_for(&src_layers);
return self.compose_and_graft(
&src_path,
&src_layers,
target_is_root,
true,
root,
ArcType::Specialize,
map,
src,
sibling,
);
}
let has_specs = self.stack_has_spec(&src_layers, &src_path);
let new_node = self
.output
.add_site_child(root, src_layers, src_path, ArcType::Specialize, map, true);
{
let n = &mut self.output.nodes[new_node.idx()];
n.origin = Some(src);
n.sibling_num_at_origin = sibling;
n.has_specs = has_specs;
}
self.add_tasks_for_node(new_node);
Ok(Some(new_node))
}
fn eval_implied_specializes(&mut self, node: NodeId) -> BuildResult<()> {
if self.node(node).parent().is_none() {
return Ok(());
}
self.find_specializes_to_propagate_to_root(node)
}
fn find_specializes_to_propagate_to_root(&mut self, node: NodeId) -> BuildResult<()> {
if self.is_relocates_placeholder_implied_arc(node) {
return Ok(());
}
if self.node(node).arc == ArcType::Specialize {
self.propagate_node_to_root(node)?;
}
let children = self.node(node).children().to_vec();
for child in children {
self.find_specializes_to_propagate_to_root(child)?;
}
Ok(())
}
fn add_ref_or_payload_arc(
&mut self,
parent: NodeId,
asset_path: &str,
prim_path: &Path,
arc: ArcType,
arc_offset: LayerOffset,
) -> BuildResult<()> {
let is_internal = asset_path.is_empty();
let parent_path = self.node(parent).path.clone();
let (target_stack, target_is_root) = if is_internal {
let stack = self.node(parent).layer_stack().to_vec();
let is_root = self.ambient_is_root_for(&stack);
(stack, is_root)
} else {
let Some(layer_index) = self.inputs.stack.find(asset_path) else {
self.errors.push(Error::UnresolvedLayer {
asset_path: asset_path.to_string(),
arc,
introduced_by: self.introducing_layer(parent),
site_path: parent_path,
});
return Ok(());
};
(self.inputs.stack.sublayer_stack(layer_index).to_vec(), false)
};
let source = if prim_path.is_empty() {
let Some(p) = self.resolve_default_prim(&target_stack)? else {
self.errors.push(Error::MissingDefaultPrim {
layer_id: self.inputs.stack.layer(target_stack[0].0).identifier.clone(),
arc,
site_path: parent_path,
});
return Ok(());
};
p
} else {
prim_path.clone()
};
let rep = target_stack[0].0;
if self.frame_skip() && self.find_duplicate(rep, &source) {
return Ok(());
}
if !self.arc_target_in_bounds(parent, rep, &source) {
self.errors
.push(Error::ArcCycle(self.cycle_error(parent, arc, rep, source)));
return Ok(());
}
if let Some((reloc_source, reloc_layer)) = self.prohibiting_relocation_source(&target_stack, &source) {
self.errors.push(Error::ProhibitedRelocationSource {
arc,
site: parent_path.clone(),
site_layer: self.introducing_layer(parent),
target: source.clone(),
target_layer: self.inputs.stack.layer(rep).identifier.clone(),
reloc_source,
reloc_layer,
composing: self.site.path.clone(),
});
return Ok(());
}
let mut map = MapFunction::from_pair(source.clone(), parent_path.strip_all_variant_selections())
.with_time_offset(arc_offset);
map = self.fold_relocates_into(map, parent);
if is_internal {
map = map.with_root_identity();
}
if !source.is_root_prim() {
let before = self.errors.len();
let grafted = self.compose_and_graft(
&source,
&target_stack,
target_is_root,
self.frame_skip(),
parent,
arc,
map,
parent,
0,
)?;
let hit_cycle = self.errors[before..].iter().any(|e| matches!(e, Error::ArcCycle(_)));
let unresolved = hit_cycle && !grafted.is_some_and(|g| self.subtree_has_specs(g));
if unresolved {
self.errors.push(Error::UnresolvedPrimPath {
arc,
target_layer: self.inputs.stack.layer(rep).identifier.clone(),
prim_path: source.clone(),
introduced_by: self.introducing_layer(parent),
site_path: parent_path.clone(),
});
}
return Ok(());
}
let empty = !self.stack_has_spec(&target_stack, &source);
if empty && !is_internal && arc == ArcType::Payload {
self.errors.push(Error::UnresolvedPrimPath {
arc,
target_layer: self.inputs.stack.layer(rep).identifier.clone(),
prim_path: source.clone(),
introduced_by: self.introducing_layer(parent),
site_path: parent_path.clone(),
});
}
let new_node = self
.output
.add_site_child(parent, target_stack, source, arc, map, false);
if empty {
self.output.nodes[new_node.idx()].flags |= NodeFlags::CULLED;
return Ok(());
}
self.add_tasks_for_node(new_node);
Ok(())
}
fn resolve_default_prim(&self, target_stack: &[(LayerId, LayerOffset)]) -> BuildResult<Option<Path>> {
let root_layer = target_stack
.iter()
.map(|&(li, _)| li)
.find(|li| !self.inputs.stack.session_layers().contains(li))
.unwrap_or(target_stack[0].0);
let Some(value) = self
.inputs
.stack
.layer(root_layer)
.try_get(&Path::abs_root(), FieldKey::DefaultPrim.as_str())?
else {
return Ok(None);
};
match value.into_owned() {
Value::Token(name) | Value::String(name) => Ok(Path::new(&format!("/{name}")).ok()),
_ => Ok(None),
}
}
fn introducing_layer(&self, node: NodeId) -> String {
self.inputs.stack.layer(self.node(node).layer_id()).identifier.clone()
}
fn cycle_error(
&self,
parent: NodeId,
closing_arc: ArcType,
closing_layer: LayerId,
closing_path: Path,
) -> CycleChain {
let mut hops = Vec::new();
let mut cur = parent;
let root_node = loop {
let n = self.node(cur);
match n.parent() {
Some(p) if !self.node(p).is_inert() => {
hops.push(CycleHop {
arc: n.arc,
layer: self.introducing_layer(cur),
path: n.path.clone(),
});
cur = p;
}
_ => break cur,
}
};
hops.reverse();
let mut composing = self.site.path.clone();
let mut root_layer = self.introducing_layer(root_node);
let mut frame_hops = Vec::new();
let mut frame = self.frame;
while let Some(f) = frame {
frame_hops.push(CycleHop {
arc: f.arc,
layer: self.inputs.stack.layer(f.requested_layer).identifier.clone(),
path: composing.clone(),
});
composing = f.root_path.clone();
root_layer = self.inputs.stack.layer(f.requested_layer).identifier.clone();
frame = f.previous;
}
frame_hops.reverse();
frame_hops.extend(hops);
let mut hops = frame_hops;
hops.push(CycleHop {
arc: closing_arc,
layer: self.inputs.stack.layer(closing_layer).identifier.clone(),
path: closing_path,
});
CycleChain {
composing,
root_layer,
hops,
}
}
fn subtree_has_specs(&self, node: NodeId) -> bool {
let n = self.node(node);
if n.has_specs() {
return true;
}
n.children().iter().any(|&c| self.subtree_has_specs(c))
}
fn prohibiting_relocation_source(&self, ambient: &[(LayerId, LayerOffset)], path: &Path) -> Option<(Path, String)> {
let mut p = Some(path.clone());
while let Some(cur) = p {
if let Some(layer) = self.inputs.stack.relocation_source_layer(ambient, &cur) {
return Some((cur, layer.to_string()));
}
if cur.is_abs_root() {
break;
}
p = cur.parent();
}
None
}
fn class_arc_is_cycle(&self, parent: NodeId, rep: LayerId, path: &Path) -> bool {
let mut cur = parent;
while cur.is_valid() {
let n = self.node(cur);
if n.layer_id() == rep && n.path.is_nested_with(path) {
return true;
}
cur = n.parent().unwrap_or(NodeId::INVALID);
}
for (f, search) in self.frame_sites(path) {
if search == f.root_path && f.graph.node_using_site(rep, &search).is_some() {
return true;
}
}
false
}
fn arc_target_in_bounds(&self, parent: NodeId, root_layer: LayerId, path: &Path) -> bool {
let mut depth = 1;
let mut cur = parent;
while cur.is_valid() {
let n = self.node(cur);
if n.layer_id() == root_layer && n.path.is_nested_with(path) {
return false;
}
depth += 1;
cur = n.parent().unwrap_or(NodeId::INVALID);
}
for (f, search) in self.frame_sites(path) {
let at_root = search == f.root_path && f.graph.node_using_site(root_layer, &search).is_some();
let above_root = search != f.root_path
&& f.root_path.has_prefix(&search)
&& f.graph.node_using_site(root_layer, &f.root_path).is_some();
if at_root || above_root {
return false;
}
depth += 1;
}
depth <= MAX_DEPTH
}
fn ambient_is_root_for(&self, layers: &[(LayerId, LayerOffset)]) -> bool {
self.site.is_root && layers == self.site.ambient
}
fn stack_has_spec(&self, stack: &[(LayerId, LayerOffset)], path: &Path) -> bool {
stack.iter().any(|&(li, _)| self.inputs.stack.layer(li).has_spec(path))
}
fn node_can_contribute_specs(&self, node: NodeId) -> bool {
let n = self.node(node);
!n.is_inert() && n.has_specs
}
fn fold_relocates_into(&self, map: MapFunction, parent: NodeId) -> MapFunction {
if !self.inputs.stack.has_relocates() {
return map;
}
let layers = self.node(parent).layer_stack();
let site = self.node(parent).path.strip_all_variant_selections();
let exclude = (!self.root_contributes).then_some(&self.site.path);
match self.inputs.stack.relocates_expression_at(layers, &site, exclude) {
Some(reloc) => reloc.compose(&map),
None => map,
}
}
fn node_can_contribute_ancestral(&self, node: NodeId, path: &Path) -> bool {
let n = self.node(node);
if n.restriction_depth == 0 {
return !n.is_inert();
}
n.restriction_depth as usize > path.element_count()
}
fn node_can_contribute_variant(&self, node: NodeId, vset_path: &Path, is_ancestral: bool) -> bool {
if is_ancestral {
self.node_can_contribute_ancestral(node, vset_path)
} else {
self.node_can_contribute_specs(node)
}
}
fn compose_token_children(&self, node: NodeId, path: &Path, key: ChildrenKey) -> BuildResult<Vec<String>> {
let mut out: Vec<String> = Vec::new();
for &(layer, _) in self.node(node).layer_stack() {
let Some(value) = self.inputs.stack.layer(layer).try_get(path, key.as_str())? else {
continue;
};
if let Value::TokenVec(names) = value.into_owned() {
for name in names {
if !out.contains(&name) {
out.push(name);
}
}
}
}
Ok(out)
}
fn node(&self, id: NodeId) -> &super::prim_graph::Node {
&self.output[id.idx()]
}
fn node_slice(&self, node: NodeId) -> &[super::prim_graph::Node] {
&self.output[node.idx()..=node.idx()]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ar::DefaultResolver;
fn stack(text: &str) -> LayerGraph {
let data = crate::usda::parser::Parser::new(text).parse().expect("parse usda");
let layer = crate::sdf::Layer::new("root.usd", Box::new(crate::usda::TextReader::from_data(data)));
LayerGraph::from_layers(vec![layer], 0, Box::new(DefaultResolver::new()), true)
}
fn multi_stack(layers: &[(&str, &str)]) -> LayerGraph {
session_stack(layers, 0)
}
fn build(stack: &LayerGraph, prim: &str) -> Option<PrimIndexGraph> {
let ctx = CompositionContext::default();
let ambient = stack.root_layer_stack();
Indexer::new(stack, &ctx, &HashMap::new(), ambient, true)
.build(&Path::from(prim))
.expect("indexer build")
.graph
}
fn session_stack(layers: &[(&str, &str)], session: usize) -> LayerGraph {
let layers = layers
.iter()
.map(|(id, text)| {
let data = crate::usda::parser::Parser::new(text).parse().expect("parse usda");
crate::sdf::Layer::new(*id, Box::new(crate::usda::TextReader::from_data(data)))
})
.collect();
LayerGraph::from_layers(layers, session, Box::new(DefaultResolver::new()), true)
}
#[test]
fn internal_default_ref_skips_session() {
let s = session_stack(
&[
("session.usd", "#usda 1.0\n"),
(
"root.usd",
"#usda 1.0\n(\n defaultPrim = \"Target\"\n)\ndef \"Target\" { custom double x = 1 }\ndef \"P\" (\n references = <>\n) {}\n",
),
],
1,
);
let graph = build(&s, "/P").expect("an internal default reference resolves via the root layer");
assert!(
graph
.iter()
.any(|n| n.arc == ArcType::Reference && n.path.as_str() == "/Target" && n.has_specs()),
"the internal default reference targets the root layer's defaultPrim, got {:?}",
graph.iter().map(|n| n.path.as_str()).collect::<Vec<_>>()
);
}
#[test]
fn local_prim_supported() {
let s = stack("#usda 1.0\ndef \"World\" {\n def \"Sphere\" {}\n}\n");
let graph = build(&s, "/World").expect("a purely local prim is supported");
let arcs: Vec<ArcType> = graph.iter().filter(|n| !n.is_inert()).map(|n| n.arc).collect();
assert_eq!(arcs, vec![ArcType::Root]);
}
#[test]
fn local_inherit_composed() {
let s = stack("#usda 1.0\nclass \"C\" { custom double x = 1 }\ndef \"World\" (\n inherits = </C>\n) {\n}\n");
let graph = build(&s, "/World").expect("a local inherit to a root class is composed");
assert!(
graph
.iter()
.any(|n| n.arc == ArcType::Inherit && n.path.as_str() == "/C" && n.has_specs()),
"the inherit arc to /C contributes the class opinion"
);
}
#[test]
fn local_specialize_composed() {
let s =
stack("#usda 1.0\nclass \"C\" { custom double x = 1 }\ndef \"World\" (\n specializes = </C>\n) {\n}\n");
let graph = build(&s, "/World").expect("a local specialize to a root class is composed");
assert!(
graph
.iter()
.any(|n| n.arc == ArcType::Specialize && n.path.as_str() == "/C" && n.has_specs() && !n.is_inert()),
"the specialize arc to /C contributes the class opinion"
);
let order: Vec<ArcType> = graph.strength_order.iter().map(|&id| graph[id.idx()].arc).collect();
assert_eq!(
order.last(),
Some(&ArcType::Specialize),
"the specialize opinion is weakest, got {order:?}"
);
}
#[test]
fn authored_variant_composed() {
let s = stack(
"#usda 1.0\ndef \"World\" (\n variantSets = \"v\"\n variants = { string v = \"hi\" }\n) {\n variantSet \"v\" = {\n \"hi\" { custom double x = 1 }\n \"lo\" { custom double x = 2 }\n }\n}\n",
);
let graph = build(&s, "/World").expect("a prim with an authored variant selection is composed");
assert!(
graph
.iter()
.any(|n| n.arc == ArcType::Variant && n.path.as_str() == "/World{v=hi}" && n.has_specs()),
"the selected variant {{v=hi}} contributes a node, got {:?}",
graph.iter().map(|n| n.path.as_str()).collect::<Vec<_>>()
);
}
#[test]
fn variant_no_selection_unselected() {
let s = stack(
"#usda 1.0\ndef \"World\" (\n variantSets = \"v\"\n) {\n variantSet \"v\" = {\n \"a\" { custom double x = 1 }\n \"b\" { custom double x = 2 }\n }\n}\n",
);
let graph = build(&s, "/World").expect("a prim with an unselected variant set is composed");
assert!(
graph.iter().all(|n| n.arc != ArcType::Variant),
"no variant arc is added without a selection, got {:?}",
graph.iter().map(|n| n.path.as_str()).collect::<Vec<_>>()
);
}
#[test]
fn subroot_inherit_composed() {
let s = stack(
"#usda 1.0\ndef \"Scope\" {\n class \"C\" { custom double x = 1 }\n}\ndef \"World\" (\n inherits = </Scope/C>\n) {\n}\n",
);
let graph = build(&s, "/World").expect("a sub-root class inherit is composed");
assert!(
graph
.iter()
.any(|n| n.arc == ArcType::Inherit && n.path.as_str() == "/Scope/C" && n.has_specs()),
"the sub-root inherit arc to /Scope/C contributes the class opinion"
);
}
#[test]
fn implied_class_from_reference() {
let s = multi_stack(&[
(
"root.usd",
"#usda 1.0\ndef \"Model\" (\n references = @ref.usd@</Model>\n) {}\nclass \"Class\" { custom double x = 1 }\n",
),
(
"ref.usd",
"#usda 1.0\ndef \"Model\" (\n inherits = </Class>\n) {}\nclass \"Class\" {}\n",
),
]);
let graph = build(&s, "/Model").expect("reference + class is composed by the indexer");
let class_layers: Vec<LayerId> = graph
.iter()
.filter(|n| n.arc == ArcType::Inherit && n.path.as_str() == "/Class")
.map(|n| n.layer_id())
.collect();
let root_id = s.find("root.usd").expect("root.usd present");
let ref_id = s.find("ref.usd").expect("ref.usd present");
assert!(
class_layers.contains(&root_id) && class_layers.contains(&ref_id),
"the class is composed in both the referenced and referencing layers, got {class_layers:?}"
);
}
#[test]
fn internal_reference_composed() {
let s = stack(
"#usda 1.0\ndef \"Base\" { custom double x = 1 }\ndef \"World\" (\n references = </Base>\n) {\n}\n",
);
let graph = build(&s, "/World").expect("an internal reference to a root prim is composed");
assert!(
graph
.iter()
.any(|n| n.arc == ArcType::Reference && n.path.as_str() == "/Base" && n.has_specs()),
"the internal reference arc to /Base contributes its opinion"
);
}
#[test]
fn reference_diamond_two_targets() {
let s = multi_stack(&[
(
"root.usd",
"#usda 1.0\ndef \"Root\" (\n references = [@A.usd@</A>, @B.usd@</B>]\n) {}\n",
),
("A.usd", "#usda 1.0\ndef \"A\" (\n references = @C.usd@</C>\n) {}\n"),
("B.usd", "#usda 1.0\ndef \"B\" (\n references = @C.usd@</C>\n) {}\n"),
("C.usd", "#usda 1.0\ndef \"C\" { custom double x = 1 }\n"),
]);
let graph = build(&s, "/Root").expect("a pure reference diamond is composed by the indexer");
let c_nodes = graph.iter().filter(|n| n.path.as_str() == "/C").count();
assert_eq!(c_nodes, 2, "the shared reference target appears once per arc path");
}
#[test]
fn ancestral_reference_propagates_to_child() {
let s = multi_stack(&[
(
"root.usd",
"#usda 1.0\ndef \"Root\" (\n references = @A.usd@</A>\n) {}\n",
),
(
"A.usd",
"#usda 1.0\ndef \"A\" {\n def \"Child\" { custom double x = 1 }\n}\n",
),
]);
let ctx = CompositionContext::default();
let ambient = s.root_layer_stack();
let root_index = PrimIndex::build_with_context(&Path::from("/Root"), &s, &ctx).expect("root index build");
let mut cached = HashMap::new();
cached.insert(Path::from("/Root"), root_index);
let child = Indexer::new(&s, &ctx, &cached, ambient, true)
.build(&Path::from("/Root/Child"))
.expect("indexer build")
.graph
.expect("child composed by indexer");
assert!(
child
.iter()
.any(|n| n.path.as_str() == "/A/Child" && n.arc == ArcType::Reference && n.has_specs()),
"the ancestral reference contributes the child's opinion at the deepened site"
);
}
}