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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions editor/src/messages/portfolio/document_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
"graphene_core::transform_nodes::FreezeRealTimeNode",
"graphene_core::vector::SubpathSegmentLengthsNode",
"core_types::vector::SubpathSegmentLengthsNode",
// The deleted debug Option trio degrades to a passthrough of its single input (audit resolution 8)
// The deleted debug Option trio degrades to a passthrough of its single input
"graphene_core::ops::SizeOfNode",
"graphene_core::debug::SizeOfNode",
"graphene_core::ops::SomeNode",
Expand Down Expand Up @@ -2765,7 +2765,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne
}
}

// The removed Attach Attribute node (merged into Write Attribute per audit resolution 6) degrades to a passthrough of its
// The removed Attach Attribute node degrades to a passthrough of its
// content: its eager whole-list source input cannot be mechanically rewired as Write Attribute's lazy per-item value producer.
if let Some(DefinitionIdentifier::ProtoNode(identifier)) = document.network_interface.reference(node_id, network_path)
&& identifier.as_str().ends_with("::AttachAttributeNode")
Expand Down
44 changes: 44 additions & 0 deletions node-graph/interpreted-executor/src/dynamic_executor/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,50 @@ fn position_value_converts_through_the_vector_input_adapter() {
assert!(result.is_some(), "The position should arrive as an Item<Vector> single-anchor path");
}

// A scalar wire feeding a `DVec2` connector splats into both axes through the input adapter's `Convert` row
#[test]
fn number_value_splats_through_the_vec2_input_adapter() {
let number_node = ProtoNode::value(ConstructionArgs::Value(TaggedValue::F64(-60.).into()), vec![NodeId(0)]);

let mut input_adapter_node = ProtoNode::value(ConstructionArgs::Nodes(vec![NodeId(0)]), vec![NodeId(1)]);
input_adapter_node.identifier = ProtoNodeIdentifier::new("input_adapter<DVec2>");

let network = ProtoNetwork {
inputs: vec![],
output: NodeId(1),
nodes: vec![(NodeId(0), number_node), (NodeId(1), input_adapter_node)],
};
let mut typing_context = TypingContext::new(&crate::node_registry::NODE_REGISTRY);
typing_context.update(&network).expect("An f64 wire should resolve the adapter's splat conversion row");
let tree = futures::executor::block_on(BorrowTree::new(network, &typing_context)).expect("The splat constructor should instantiate");

let context: Context = None;
let result: Option<Item<glam::DVec2>> = futures::executor::block_on(tree.eval(NodeId(1), context));
assert_eq!(result.map(|item| *item.element()), Some(glam::DVec2::splat(-60.)), "The scalar should splat into both axes");
}

// A scalar wire feeding a `String` connector formats as text through the input adapter's `Convert` row
#[test]
fn number_value_formats_through_the_string_input_adapter() {
let number_node = ProtoNode::value(ConstructionArgs::Value(TaggedValue::F64(42.).into()), vec![NodeId(0)]);

let mut input_adapter_node = ProtoNode::value(ConstructionArgs::Nodes(vec![NodeId(0)]), vec![NodeId(1)]);
input_adapter_node.identifier = ProtoNodeIdentifier::new("input_adapter<String>");

let network = ProtoNetwork {
inputs: vec![],
output: NodeId(1),
nodes: vec![(NodeId(0), number_node), (NodeId(1), input_adapter_node)],
};
let mut typing_context = TypingContext::new(&crate::node_registry::NODE_REGISTRY);
typing_context.update(&network).expect("An f64 wire should resolve the adapter's formatting conversion row");
let tree = futures::executor::block_on(BorrowTree::new(network, &typing_context)).expect("The formatting constructor should instantiate");

let context: Context = None;
let result: Option<Item<String>> = futures::executor::block_on(tree.eval(NodeId(1), context));
assert_eq!(result.map(|item| item.element().clone()), Some("42".to_string()), "The number should format as its text representation");
}

// A `List` wire feeding a `ListDyn` connector erases its element type through the input adapter's `Into` row
#[test]
fn list_wire_erases_through_the_list_dyn_input_adapter() {
Expand Down
28 changes: 18 additions & 10 deletions node-graph/interpreted-executor/src/node_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
Raster<CPU>,
Color,
Gradient,
f32,
f64,
u32,
u64,
bool,
String,
DVec2,
Expand Down Expand Up @@ -485,8 +488,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
node_types.extend(input_adapter_row!(from_element: String, element: BoxCorners));
// A number wire may feed the ranked `Item<BoxCorners>` connector, each number becoming a uniform radius for all four corners
node_types.extend(input_adapter_row!(from_element: f64, element: BoxCorners));
// Numeric wires cast between element types at a ranked connector, as `Convert` does for bare numeric wires
macro_rules! numeric_convert_node {
// The `Convert`-based counterpart of `input_adapter_row!`, for casts the std `Into` trait cannot express
macro_rules! convert_adapter_node {
(from_element: $from:ty, element: $element:ty) => {{
let entries: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
input_adapter_row!(node: ConvertItemNode, from: Item<$from>, to: Item<$element>, element: $element),
Expand All @@ -495,19 +498,24 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
entries
}};
}
macro_rules! numeric_convert_star {
macro_rules! convert_adapter_wildcard {
(from: $from:ty, to: [$($to:ty),*]) => {{
let mut entries: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = Vec::new();
$(entries.extend(numeric_convert_node!(from_element: $from, element: $to));)*
$(entries.extend(convert_adapter_node!(from_element: $from, element: $to));)*
entries
}};
}
node_types.extend(numeric_convert_star!(from: f64, to: [f32, u32, u64, i32, i64]));
node_types.extend(numeric_convert_star!(from: f32, to: [f64, u32, u64, i32, i64]));
node_types.extend(numeric_convert_star!(from: u32, to: [f64, f32, u64, i32, i64]));
node_types.extend(numeric_convert_star!(from: u64, to: [f64, f32, u32, i32, i64]));
node_types.extend(numeric_convert_star!(from: i32, to: [f64, f32, u32, u64, i64]));
node_types.extend(numeric_convert_star!(from: i64, to: [f64, f32, u32, u64, i32]));
// Numeric wires cast between numeric element types, splat to fill both axes of a `DVec2` connector, and format into a `String` connector
node_types.extend(convert_adapter_wildcard!(from: f64, to: [f32, u32, u64, i32, i64, DVec2, String]));
node_types.extend(convert_adapter_wildcard!(from: f32, to: [f64, u32, u64, i32, i64, DVec2, String]));
node_types.extend(convert_adapter_wildcard!(from: u32, to: [f64, f32, u64, i32, i64, DVec2, String]));
node_types.extend(convert_adapter_wildcard!(from: u64, to: [f64, f32, u32, i32, i64, DVec2, String]));
node_types.extend(convert_adapter_wildcard!(from: i32, to: [f64, f32, u32, u64, i64, DVec2, String]));
node_types.extend(convert_adapter_wildcard!(from: i64, to: [f64, f32, u32, u64, i32, DVec2, String]));
// Bool, position, and transform wires may feed a ranked `String` connector by formatting each element as text
node_types.extend(convert_adapter_node!(from_element: bool, element: String));
node_types.extend(convert_adapter_node!(from_element: DVec2, element: String));
node_types.extend(convert_adapter_node!(from_element: DAffine2, element: String));
// The sanctioned attribute value conversions: an Item wire's elements box per cell, while a List wire boxes whole as one value
macro_rules! attribute_value_node {
(Item<$element:ty>) => {
Expand Down
8 changes: 8 additions & 0 deletions node-graph/libraries/core-types/src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ pub trait Convert<T, C>: Sized {
fn convert(self, footprint: Footprint, converter: C) -> impl Future<Output = T> + Send;
}

impl<T: ToString + Send> Convert<String, ()> for T {
/// Converts this type into a `String` using its `ToString` implementation.
#[inline]
async fn convert(self, _: Footprint, _converter: ()) -> String {
self.to_string()
}
}

/// Constructs `Self` from a single anchor point at the given position. Implemented by the vector crate's
/// path type so a position wire can convert to a single-point path without core-types depending on that crate.
pub trait FromAnchorPosition {
Expand Down
27 changes: 27 additions & 0 deletions node-graph/nodes/gcore/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,33 @@ fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> Item<Gradient> {
var_arg.downcast_ref().cloned().unwrap_or_default()
}

/// Reads the current number from within a **Map** node's loop.
#[node_macro::node(category("Context"))]
fn read_number(ctx: impl Ctx + ExtractVarArgs) -> Item<f64> {
let Ok(var_arg) = ctx.vararg(0) else { return Default::default() };
let var_arg = var_arg as &dyn std::any::Any;

if let Some(item) = var_arg.downcast_ref::<Item<f64>>() {
return item.clone();
}

// Numeric lists carry several possible element types, so probe each and widen to f64, keeping the item's attributes
if let Some(item) = var_arg.downcast_ref::<Item<f32>>() {
let (element, attributes) = item.clone().into_parts();
return Item::from_parts(element as f64, attributes);
}
if let Some(item) = var_arg.downcast_ref::<Item<u32>>() {
let (element, attributes) = item.clone().into_parts();
return Item::from_parts(element as f64, attributes);
}
if let Some(item) = var_arg.downcast_ref::<Item<u64>>() {
let (element, attributes) = item.clone().into_parts();
return Item::from_parts(element as f64, attributes);
}

Default::default()
}

#[node_macro::node(category("Context"), path(core_types::vector))]
async fn read_position(
ctx: impl Ctx + ExtractPosition,
Expand Down
1 change: 1 addition & 0 deletions node-graph/nodes/graphic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ dyn-any = { workspace = true }
glam = { workspace = true }
serde = { workspace = true }
node-macro = { workspace = true }
rand = { workspace = true }
Loading
Loading