Fix several bugs causing warning spam in the console (#4547)

This commit is contained in:
Keavon Chambers
2026-09-17 00:51:42 -07:00
committed by GitHub
parent 3f1351e3b9
commit cb30348215
4 changed files with 74 additions and 8 deletions

View File

@@ -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(),
@@ -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 {
@@ -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:?}");
}
}
@@ -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)]
@@ -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");

View File

@@ -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()
}

View File

@@ -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:?}"),

View File

@@ -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.;
@@ -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>,
#[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(