mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Replace Footprint/() call arguments with dynamically-bound Contexts (#2232)
* Implement experimental Context struct and traits * Add Ctx super trait * Checkpoint * Return Any instead of DynAny * Fix send implementation for inputs with lifetimes * Port more nodes * Uncomment nodes * Port more nodes * Port vector nodes * Partial progress (the stuff I'm more sure about) * Partial progress (the stuff that's not compiling and I'm not sure about) * Fix more errors * First pass of fixing errors introduced by rebase * Port wasm application io * Fix brush node types * Add type annotation * Fix warnings and wasm compilation * Change types for Document Node definitions * Improve debugging for footprint not found errors * Forward context in append artboard node * Fix thumbnails * Fix loading most demo artwork * Wrap output type of all nodes in future * Encode futures as part of the type * Fix document node definitions for future types * Remove Clippy warnings * Fix more things * Fix opening demo art with manual composition upgrading * Set correct type for manual composition * Fix brush * Fix tests * Update docs for deps * Fix up some node signature issues * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
0c1e96b9c6
commit
4ff2bdb04f
@@ -54,6 +54,7 @@ winit = { workspace = true }
|
||||
[dev-dependencies]
|
||||
# Workspace dependencies
|
||||
graph-craft = { workspace = true, features = ["loading"] }
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
# Required dependencies
|
||||
criterion = { version = "0.5", features = ["html_reports"]}
|
||||
|
||||
@@ -304,7 +304,7 @@ impl DocumentNode {
|
||||
match first {
|
||||
NodeInput::Value { tagged_value, .. } => {
|
||||
assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs);
|
||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
|
||||
(ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context<'static>)), ConstructionArgs::Value(tagged_value))
|
||||
}
|
||||
NodeInput::Node { node_id, output_index, lambda } => {
|
||||
assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node");
|
||||
@@ -1567,7 +1567,7 @@ mod test {
|
||||
NodeId(14),
|
||||
ProtoNode {
|
||||
identifier: "graphene_core::value::ClonedNode".into(),
|
||||
input: ProtoNodeInput::None,
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)),
|
||||
construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(vec![NodeId(1), NodeId(4)]),
|
||||
@@ -1589,7 +1589,7 @@ mod test {
|
||||
|
||||
println!("{:#?}", resolved_network[0]);
|
||||
println!("{construction_network:#?}");
|
||||
assert_eq!(resolved_network[0], construction_network);
|
||||
pretty_assertions::assert_eq!(resolved_network[0], construction_network);
|
||||
}
|
||||
|
||||
fn flat_network() -> NodeNetwork {
|
||||
|
||||
@@ -102,8 +102,8 @@ macro_rules! tagged_value {
|
||||
})
|
||||
}
|
||||
Type::Fn(_, output) => TaggedValue::from_type(output),
|
||||
Type::Future(_) => {
|
||||
None
|
||||
Type::Future(output) => {
|
||||
TaggedValue::from_type(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,7 @@ impl TaggedValue {
|
||||
Some(ty)
|
||||
}
|
||||
Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output),
|
||||
Type::Future(_) => None,
|
||||
Type::Future(fut) => TaggedValue::from_primitive_string(string, fut),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ impl Default for ProtoNode {
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ProtoNodeInput {
|
||||
/// [`ProtoNode`]s do not require any input, e.g. the value node just takes in [`ConstructionArgs`].
|
||||
/// This input will be converted to `()` as the call argument.
|
||||
None,
|
||||
/// A ManualComposition input represents an input that opts out of being resolved through the `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated
|
||||
/// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor.
|
||||
@@ -203,6 +203,7 @@ impl ProtoNode {
|
||||
if self.skip_deduplication {
|
||||
self.original_location.path.hash(&mut hasher);
|
||||
}
|
||||
|
||||
std::mem::discriminant(&self.input).hash(&mut hasher);
|
||||
match self.input {
|
||||
ProtoNodeInput::None => (),
|
||||
@@ -212,6 +213,7 @@ impl ProtoNode {
|
||||
ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher),
|
||||
ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher),
|
||||
};
|
||||
|
||||
Some(NodeId(hasher.finish()))
|
||||
}
|
||||
|
||||
@@ -224,7 +226,7 @@ impl ProtoNode {
|
||||
Self {
|
||||
identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"),
|
||||
construction_args: value,
|
||||
input: ProtoNodeInput::None,
|
||||
input: ProtoNodeInput::ManualComposition(concrete!(Context)),
|
||||
original_location: OriginalLocation {
|
||||
path: Some(path),
|
||||
inputs_exposed: vec![false; inputs_exposed],
|
||||
@@ -552,10 +554,11 @@ 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 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 format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}, found: {_found}", index + 1);
|
||||
let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::<Vec<_>>().join("\n").replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||
let mut errors = error_inputs.iter().map(format_error_list).collect::<Vec<_>>();
|
||||
errors.sort();
|
||||
let inputs = inputs.replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||
write!(
|
||||
f,
|
||||
"This node isn't compatible with the com-\n\
|
||||
@@ -651,9 +654,9 @@ impl TypingContext {
|
||||
let inputs = match node.construction_args {
|
||||
// If the node has a value input we can infer the return type from it
|
||||
ConstructionArgs::Value(ref v) => {
|
||||
assert!(matches!(node.input, ProtoNodeInput::None));
|
||||
assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context)));
|
||||
// TODO: This should return a reference to the value
|
||||
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]);
|
||||
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);
|
||||
self.inferred.insert(node_id, types.clone());
|
||||
return Ok(types);
|
||||
}
|
||||
@@ -696,6 +699,8 @@ impl TypingContext {
|
||||
match (from, to) {
|
||||
// Direct comparison of two concrete types.
|
||||
(Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2,
|
||||
// Check inner type for futures
|
||||
(Type::Future(type1), Type::Future(type2)) => type1 == type2,
|
||||
// Loose comparison of function types, where loose means that functions are considered on a "greater than or equal to" basis of its function type's generality.
|
||||
// That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature.
|
||||
// For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T.
|
||||
|
||||
Reference in New Issue
Block a user