Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::Bool(Default::default()), false),
NodeInput::value(TaggedValue::InterpolationDistribution(Default::default()), false),
NodeInput::type_default(list!(Vector), false),
NodeInput::type_default(item!(Vector), false),
],
input_metadata: vec![
("Content", "TODO").into(),
Expand Down Expand Up @@ -1533,6 +1533,26 @@ impl InputTypeConstraint {
}
}

/// Check if a type reaches the constraint through an `input_adapter`, the per-connector node which the preprocessor places ahead of
/// every ranked connector to convert or embed a convertible element, such as a color literal feeding a `Graphic` paint wire.
#[must_use]
fn satisfies_through_input_adapter(&self, ty: &Type) -> bool {
let provided = Self::type_name(ty);

interpreted_executor::node_registry::NODE_REGISTRY
.iter()
.filter(|(identifier, _)| identifier.as_str().starts_with("input_adapter<"))
.flat_map(|(_, implementations)| implementations.keys())
.any(|node_io| node_io.inputs.first().is_some_and(|from| Self::type_name(from) == provided) && self.satisfies(&node_io.return_value))
}

/// Check if a default value of this type is valid for the constraint.
#[must_use]
fn accepts_default_value(&self, ty: &Type) -> bool {
// Empty types are used when the input must come from the graph so they can be skipped
ty.nested_type() == &concrete!(()) || self.satisfies(ty) || self.satisfies_through_input_adapter(ty)
}

/// Compute the type constraint for one input. Note that this cannot use the infrastructure in the node network interface as the node is not placed in a network.
#[must_use]
fn compute_constraint_for_input(template_document_node: &NodeTemplate, name: &str, input_index: usize) -> Self {
Expand Down Expand Up @@ -1589,9 +1609,7 @@ impl InputTypeConstraint {
for (index, (constraint, input)) in all_input_constraints.iter().zip(&template_document_node.inputs).enumerate() {
if let Some(value) = input.as_value() {
let input_ty = value.ty();

// Empty types are used when the input must come from the graph so they can be skipped.
if input_ty.nested_type() != &concrete!(()) && !constraint.satisfies(&input_ty) {
if !constraint.accepts_default_value(&input_ty) {
warn!("The default value for input index {index} node {name} is {input_ty}, but does not satisfy {constraint:?}");
}
}
Expand Down Expand Up @@ -1687,6 +1705,27 @@ mod test {

editor.eval_graph().await.expect("the Origins to Polyline chain should type-resolve and evaluate");
}

// Guards the unconnected Path input, whose default must match the rank of the Morph connector it feeds
#[tokio::test]
async fn blend_resolves_and_evaluates_with_default_inputs() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
editor.draw_rect(0., 0., 10., 10.).await;

let layer = editor.active_document().metadata().all_layers().next().expect("drawing a rectangle should create a layer");
let node_id = NodeId::new();
let node_template = resolve_network_node_type("Blend").expect("the Blend definition should exist").default_node_template();
editor
.handle_message(NodeGraphMessage::InsertNode {
node_id,
node_template: Box::new(node_template),
})
.await;
editor.handle_message(NodeGraphMessage::MoveNodeToChainStart { node_id, parent: layer }).await;

editor.eval_graph().await.expect("the Blend network should type-resolve and evaluate");
}
}

#[cfg(test)]
Expand All @@ -1707,6 +1746,25 @@ mod test_type_constraints {
}
}

#[test]
fn every_definition_default_value_satisfies_its_constraint() {
let mut violations = Vec::new();
for definition in super::DOCUMENT_NODE_TYPES.values() {
let name = &definition.node_template.display_name;
let constraints = InputTypeConstraint::constraints_for_all_inputs(&definition.node_template, name);

for (index, (constraint, input)) in constraints.iter().zip(&definition.node_template.inputs).enumerate() {
if let Some(value) = input.as_value()
&& !constraint.accepts_default_value(&value.ty())
{
violations.push(format!("{name} input {index}: {} does not satisfy {constraint:?}", value.ty()));
}
}
}

assert!(violations.is_empty(), "Default values rejected by their input constraints:\n{}", violations.join("\n"));
}

#[test]
fn passthrough() {
let node_type = resolve_proto_node_type(graphene_std::ops::passthrough::IDENTIFIER).expect("passthrough node");
Expand Down
6 changes: 6 additions & 0 deletions frontend/wrapper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ pub struct WasmLog;
impl log::Log for WasmLog {
#[inline]
fn enabled(&self, metadata: &log::Metadata) -> bool {
// Dependencies that log routine rendering details at the debug level are capped so they don't flood the console
let crate_name = metadata.target().split("::").next().unwrap_or_default();
if crate_name.starts_with("vello") {
return metadata.level() <= log::Level::Info;
}

metadata.level() <= log::max_level()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ async fn request_persistence() {
match storage.persist() {
Ok(promise) => match JsFuture::from(promise).await {
Ok(value) if value.as_bool() == Some(true) => {}
Ok(_) => log::warn!("OPFS persistence was not granted; browser may evict resources under storage pressure"),
// Browsers deny this by default unless the site is bookmarked, installed, or highly engaged, so it isn't worth a warning
Ok(_) => log::trace!("OPFS persistence was not granted; browser may evict resources under storage pressure"),
Err(error) => log::warn!("OPFS persist() rejected: {error:?}"),
},
Err(error) => log::warn!("OPFS persist() threw: {error:?}"),
Expand Down
7 changes: 4 additions & 3 deletions node-graph/nodes/brush/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod basic_brush;

pub use brush_types::*;

// Fallbacks for stroke items carrying no such attribute, mirroring the `Brush Strokes` defaults below (which the node macro requires as literals)
pub(crate) const DEFAULT_DIAMETER: f64 = 40.;
pub(crate) const DEFAULT_HARDNESS: f64 = 0.;
pub(crate) const DEFAULT_FLOW: f64 = 100.;
Expand All @@ -17,9 +18,9 @@ fn brush_strokes(
_: impl Ctx,
strokes: List<Stroke>,
color: List<Color>,
#[default(DEFAULT_DIAMETER)] diameter: Item<f64>,
#[default(DEFAULT_HARDNESS)] hardness: Item<Percentage>,
#[default(DEFAULT_FLOW)] flow: Item<Percentage>,
#[default(40.)] diameter: Item<f64>,
Comment thread
Keavon marked this conversation as resolved.
#[default(0.)] hardness: Item<Percentage>,
#[default(100.)] flow: Item<Percentage>,
) -> List<Graphic> {
let (diameter, hardness, flow) = (diameter.into_element(), hardness.into_element(), flow.into_element());
List::new_from_item(
Expand Down
Loading