mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-03 23:48:12 +08:00
Fix several bugs causing warning spam in the console (#4547)
This commit is contained in:
@@ -480,7 +480,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
|||||||
NodeInput::value(TaggedValue::F64(10.), false),
|
NodeInput::value(TaggedValue::F64(10.), false),
|
||||||
NodeInput::value(TaggedValue::Bool(Default::default()), false),
|
NodeInput::value(TaggedValue::Bool(Default::default()), false),
|
||||||
NodeInput::value(TaggedValue::InterpolationDistribution(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![
|
input_metadata: vec![
|
||||||
("Content", "TODO").into(),
|
("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.
|
/// 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]
|
#[must_use]
|
||||||
fn compute_constraint_for_input(template_document_node: &NodeTemplate, name: &str, input_index: usize) -> Self {
|
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() {
|
for (index, (constraint, input)) in all_input_constraints.iter().zip(&template_document_node.inputs).enumerate() {
|
||||||
if let Some(value) = input.as_value() {
|
if let Some(value) = input.as_value() {
|
||||||
let input_ty = value.ty();
|
let input_ty = value.ty();
|
||||||
|
if !constraint.accepts_default_value(&input_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) {
|
|
||||||
warn!("The default value for input index {index} node {name} is {input_ty}, but does not satisfy {constraint:?}");
|
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");
|
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)]
|
#[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]
|
#[test]
|
||||||
fn passthrough() {
|
fn passthrough() {
|
||||||
let node_type = resolve_proto_node_type(graphene_std::ops::passthrough::IDENTIFIER).expect("passthrough node");
|
let node_type = resolve_proto_node_type(graphene_std::ops::passthrough::IDENTIFIER).expect("passthrough node");
|
||||||
|
|||||||
@@ -173,6 +173,12 @@ pub struct WasmLog;
|
|||||||
impl log::Log for WasmLog {
|
impl log::Log for WasmLog {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
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()
|
metadata.level() <= log::max_level()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,8 @@ async fn request_persistence() {
|
|||||||
match storage.persist() {
|
match storage.persist() {
|
||||||
Ok(promise) => match JsFuture::from(promise).await {
|
Ok(promise) => match JsFuture::from(promise).await {
|
||||||
Ok(value) if value.as_bool() == Some(true) => {}
|
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() rejected: {error:?}"),
|
||||||
},
|
},
|
||||||
Err(error) => log::warn!("OPFS persist() threw: {error:?}"),
|
Err(error) => log::warn!("OPFS persist() threw: {error:?}"),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub mod basic_brush;
|
|||||||
|
|
||||||
pub use brush_types::*;
|
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_DIAMETER: f64 = 40.;
|
||||||
pub(crate) const DEFAULT_HARDNESS: f64 = 0.;
|
pub(crate) const DEFAULT_HARDNESS: f64 = 0.;
|
||||||
pub(crate) const DEFAULT_FLOW: f64 = 100.;
|
pub(crate) const DEFAULT_FLOW: f64 = 100.;
|
||||||
@@ -17,9 +18,9 @@ fn brush_strokes(
|
|||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
strokes: List<Stroke>,
|
strokes: List<Stroke>,
|
||||||
color: List<Color>,
|
color: List<Color>,
|
||||||
#[default(DEFAULT_DIAMETER)] diameter: Item<f64>,
|
#[default(40.)] diameter: Item<f64>,
|
||||||
#[default(DEFAULT_HARDNESS)] hardness: Item<Percentage>,
|
#[default(0.)] hardness: Item<Percentage>,
|
||||||
#[default(DEFAULT_FLOW)] flow: Item<Percentage>,
|
#[default(100.)] flow: Item<Percentage>,
|
||||||
) -> List<Graphic> {
|
) -> List<Graphic> {
|
||||||
let (diameter, hardness, flow) = (diameter.into_element(), hardness.into_element(), flow.into_element());
|
let (diameter, hardness, flow) = (diameter.into_element(), hardness.into_element(), flow.into_element());
|
||||||
List::new_from_item(
|
List::new_from_item(
|
||||||
|
|||||||
Reference in New Issue
Block a user