mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
Improve older document upgrading compatibility and make node type errors clearer (#2201)
* Improve older document upgrading compatibility and make node type errors clearer Misc. * Fixes * Avoid unwrap
This commit is contained in:
@@ -20,7 +20,7 @@ pub mod types {
|
||||
pub type PixelLength = f64;
|
||||
/// Non negative
|
||||
pub type Length = f64;
|
||||
/// 0.- 1.
|
||||
/// 0 to 1
|
||||
pub type Fraction = f64;
|
||||
pub type IntegerCount = u32;
|
||||
/// Int input with randomization button
|
||||
|
||||
@@ -110,7 +110,7 @@ impl NodeIOTypes {
|
||||
impl core::fmt::Debug for NodeIOTypes {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"node({}) -> {}",
|
||||
"node({}) → {}",
|
||||
[&self.call_argument].into_iter().chain(&self.inputs).map(|input| input.to_string()).collect::<Vec<_>>().join(", "),
|
||||
self.return_value
|
||||
))
|
||||
@@ -292,13 +292,13 @@ fn format_type(ty: &str) -> String {
|
||||
impl core::fmt::Debug for Type {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Generic(arg0) => write!(f, "Generic({arg0})"),
|
||||
Self::Generic(arg0) => write!(f, "Generic<{arg0}>"),
|
||||
#[cfg(feature = "type_id_logging")]
|
||||
Self::Concrete(arg0) => write!(f, "Concrete({}, {:?})", arg0.name, arg0.id),
|
||||
Self::Concrete(arg0) => write!(f, "Concrete<{}, {:?}>", arg0.name, arg0.id),
|
||||
#[cfg(not(feature = "type_id_logging"))]
|
||||
Self::Concrete(arg0) => write!(f, "Concrete({})", format_type(&arg0.name)),
|
||||
Self::Fn(arg0, arg1) => write!(f, "({arg0:?} -> {arg1:?})"),
|
||||
Self::Future(arg0) => write!(f, "Future({arg0:?})"),
|
||||
Self::Concrete(arg0) => write!(f, "Concrete<{}>", format_type(&arg0.name)),
|
||||
Self::Fn(arg0, arg1) => write!(f, "{arg0:?} → {arg1:?}"),
|
||||
Self::Future(arg0) => write!(f, "Future<{arg0:?}>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +308,7 @@ impl std::fmt::Display for Type {
|
||||
match self {
|
||||
Type::Generic(name) => write!(f, "{name}"),
|
||||
Type::Concrete(ty) => write!(f, "{}", format_type(&ty.name)),
|
||||
Type::Fn(input, output) => write!(f, "({input} -> {output})"),
|
||||
Type::Fn(input, output) => write!(f, "{input} → {output}"),
|
||||
Type::Future(ty) => write!(f, "Future<{ty}>"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,7 +588,7 @@ impl ConcatElement for GraphicGroup {
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn sample_points<F: 'n + Send + Copy>(
|
||||
#[implementations(
|
||||
(),
|
||||
@@ -815,7 +815,7 @@ async fn poisson_disk_points<F: 'n + Send>(
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn subpath_segment_lengths<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
@@ -979,6 +979,8 @@ async fn morph<F: 'n + Send + Copy>(
|
||||
let target = target.eval(footprint).await;
|
||||
let mut result = VectorData::empty();
|
||||
|
||||
let time = time.clamp(0., 1.);
|
||||
|
||||
// Lerp styles
|
||||
result.alpha_blending = if time < 0.5 { source.alpha_blending } else { target.alpha_blending };
|
||||
result.style = source.style.lerp(&target.style, time);
|
||||
|
||||
@@ -324,7 +324,7 @@ impl ProtoNetwork {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remsove
|
||||
// TODO: Remove
|
||||
/// Create a hashmap with the list of nodes this proto network depends on/uses as inputs.
|
||||
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
|
||||
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
|
||||
@@ -552,27 +552,19 @@ impl core::fmt::Debug for GraphErrorType {
|
||||
GraphErrorType::NoImplementations => write!(f, "No implementations found"),
|
||||
GraphErrorType::NoConstructor => write!(f, "No construct found for node"),
|
||||
GraphErrorType::InvalidImplementations { inputs, error_inputs } => {
|
||||
let ordinal = |x: usize| match x.to_string().as_str() {
|
||||
x if x.ends_with('1') && !x.ends_with("11") => format!("{x}st"),
|
||||
x if x.ends_with('2') && !x.ends_with("12") => format!("{x}nd"),
|
||||
x if x.ends_with('3') && !x.ends_with("13") => format!("{x}rd"),
|
||||
x => format!("{x}th"),
|
||||
};
|
||||
let format_index = |index: usize| if index == 0 { "primary".to_string() } else { format!("{} secondary", ordinal(index)) };
|
||||
let format_error = |(index, (real, expected)): &(usize, (Type, Type))| format!("• The {} input expected {} but found {}", format_index(*index), expected, real);
|
||||
let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}", index + 1);
|
||||
let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::<Vec<_>>().join("\n");
|
||||
let errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
|
||||
let mut errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
|
||||
errors.sort();
|
||||
write!(
|
||||
f,
|
||||
"Node graph type error! If this just appeared while editing the graph,\n\
|
||||
consider using undo to go back and try another way to connect the nodes.\n\
|
||||
"This node isn't compatible with the com-\n\
|
||||
bination of types for the data it is given:\n\
|
||||
{inputs}\n\
|
||||
\n\
|
||||
No node implementation exists for type:\n\
|
||||
({inputs})\n\
|
||||
\n\
|
||||
Caused by{}:\n\
|
||||
Each invalid input should be replaced by\n\
|
||||
data with one of these supported types:\n\
|
||||
{}",
|
||||
if errors.len() > 1 { " one of" } else { "" },
|
||||
errors.join("\n")
|
||||
)
|
||||
}
|
||||
@@ -679,7 +671,8 @@ impl TypingContext {
|
||||
};
|
||||
|
||||
// Get the node input type from the proto node declaration
|
||||
let input = match node.input {
|
||||
// TODO: When removing automatic composition, rename this to just `call_argument`
|
||||
let primary_input_or_call_argument = match node.input {
|
||||
ProtoNodeInput::None => concrete!(()),
|
||||
ProtoNodeInput::ManualComposition(ref ty) => ty.clone(),
|
||||
ProtoNodeInput::Node(id) | ProtoNodeInput::NodeLambda(id) => {
|
||||
@@ -687,6 +680,7 @@ impl TypingContext {
|
||||
input.return_value.clone()
|
||||
}
|
||||
};
|
||||
let using_manual_composition = matches!(node.input, ProtoNodeInput::ManualComposition(_) | ProtoNodeInput::None);
|
||||
let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?;
|
||||
|
||||
if let Some(index) = inputs.iter().position(|p| {
|
||||
@@ -724,7 +718,7 @@ impl TypingContext {
|
||||
// List of all implementations that match the input types
|
||||
let valid_output_types = impls
|
||||
.keys()
|
||||
.filter(|node_io| valid_subtype(&node_io.call_argument, &input) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_subtype(p1, p2)))
|
||||
.filter(|node_io| valid_subtype(&node_io.call_argument, &primary_input_or_call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_subtype(p1, p2)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Attempt to substitute generic types with concrete types and save the list of results
|
||||
@@ -733,10 +727,10 @@ impl TypingContext {
|
||||
.map(|node_io| {
|
||||
collect_generics(node_io)
|
||||
.iter()
|
||||
.try_for_each(|generic| check_generic(node_io, &input, &inputs, generic).map(|_| ()))
|
||||
.try_for_each(|generic| check_generic(node_io, &primary_input_or_call_argument, &inputs, generic).map(|_| ()))
|
||||
.map(|_| {
|
||||
if let Type::Generic(out) = &node_io.return_value {
|
||||
((*node_io).clone(), check_generic(node_io, &input, &inputs, out).unwrap())
|
||||
((*node_io).clone(), check_generic(node_io, &primary_input_or_call_argument, &inputs, out).unwrap())
|
||||
} else {
|
||||
((*node_io).clone(), node_io.return_value.clone())
|
||||
}
|
||||
@@ -752,14 +746,18 @@ impl TypingContext {
|
||||
let mut best_errors = usize::MAX;
|
||||
let mut error_inputs = Vec::new();
|
||||
for node_io in impls.keys() {
|
||||
let current_errors = [&input]
|
||||
let current_errors = [&primary_input_or_call_argument]
|
||||
.into_iter()
|
||||
.chain(&inputs)
|
||||
.cloned()
|
||||
.zip([&node_io.call_argument].into_iter().chain(&node_io.inputs).cloned())
|
||||
.enumerate()
|
||||
.filter(|(_, (p1, p2))| !valid_subtype(p1, p2))
|
||||
.map(|(index, ty)| (node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index), ty))
|
||||
.map(|(index, ty)| {
|
||||
let i = node.original_location.inputs(index).min_by_key(|s| s.node.len()).map(|s| s.index).unwrap_or(index);
|
||||
let i = if using_manual_composition { i } else { i + 1 };
|
||||
(i, ty)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if current_errors.len() < best_errors {
|
||||
best_errors = current_errors.len();
|
||||
@@ -769,7 +767,17 @@ impl TypingContext {
|
||||
error_inputs.push(current_errors);
|
||||
}
|
||||
}
|
||||
let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
|
||||
let inputs = [&primary_input_or_call_argument]
|
||||
.into_iter()
|
||||
.chain(&inputs)
|
||||
.enumerate()
|
||||
// TODO: Make the following line's if statement conditional on being a call argument or primary input
|
||||
.filter_map(|(i, t)| {
|
||||
let i = if using_manual_composition { i } else { i + 1 };
|
||||
if i == 0 { None } else { Some(format!("• Input {i}: {t}")) }
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })])
|
||||
}
|
||||
[(org_nio, _)] => {
|
||||
@@ -794,13 +802,13 @@ impl TypingContext {
|
||||
return Ok(org_nio.clone());
|
||||
}
|
||||
}
|
||||
let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
|
||||
let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
|
||||
let valid = valid_output_types.into_iter().cloned().collect();
|
||||
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
|
||||
}
|
||||
|
||||
_ => {
|
||||
let inputs = [&input].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
|
||||
let inputs = [&primary_input_or_call_argument].into_iter().chain(&inputs).map(|t| t.to_string()).collect::<Vec<_>>().join(", ");
|
||||
let valid = valid_output_types.into_iter().cloned().collect();
|
||||
Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })])
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ fn init_logging() {
|
||||
}
|
||||
|
||||
// Migrations are done in the editor which is unfortunately not available here.
|
||||
// TODO: remove this and share migrations between the edtior and the CLI.
|
||||
// TODO: remove this and share migrations between the editor and the CLI.
|
||||
fn fix_nodes(network: &mut NodeNetwork) {
|
||||
for node in network.nodes.values_mut() {
|
||||
match &mut node.implementation {
|
||||
|
||||
@@ -222,6 +222,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
),
|
||||
),
|
||||
// Filters
|
||||
// TODO: Move these filters to the new node macro and put them in `graphene_core::raster::adjustments`, then add them to the document upgrade script which moves many of the adjustment nodes from `graphene_core::raster` to `graphene_core::raster::adjustments`
|
||||
(
|
||||
ProtoNodeIdentifier::new("graphene_core::raster::BrightnessContrastNode"),
|
||||
|args| {
|
||||
|
||||
Reference in New Issue
Block a user