diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 0ec3a05294..39188756ff 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -174,12 +174,8 @@ fn document_node_definitions() -> HashMap HashMap HashMap HashMap String { result } -/// Serialized proto identifiers of the Merge and Artboard layer internals that the -/// leveled-records flip retires. When the flip lands, `document_migration_reset_node_definition` -/// starts matching these, so every pre-flip document rebuilds its layer definitions through the -/// same reset mechanism as the `SourceNodeIdNode` entry there. A test pins each marker to the -/// live identifier so a pre-flip rename cannot silently invalidate the staged strings. +/// Serialized proto identifiers of the pre-flip Merge and Artboard layer internals. +/// A document containing any of them predates the leveled-records flip and rebuilds its +/// layer definitions through the same reset mechanism as the `SourceNodeIdNode` entry in +/// `document_migration_reset_node_definition`. pub const FLIP_RESET_NODE_MARKERS: &[&str] = &["graphic_nodes::graphic::WriteAttributeNode"]; pub fn document_migration_reset_node_definition(document_serialized_content: &str) -> bool { @@ -1052,6 +1051,12 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st return true; } + // The leveled-records flip replaced the layer internals; documents from before it rebuild + // their layer definitions. + if FLIP_RESET_NODE_MARKERS.iter().any(|marker| document_serialized_content.contains(marker)) { + return true; + } + false } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index cadb789a0f..90d19bcc9f 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -11,7 +11,7 @@ use core_types::node::Node; use core_types::registry::{EdgeHandle, edge_type}; use core_types::transform::Footprint; use core_types::uuid::NodeId; -use core_types::value::record_value_edge; +use core_types::value::{leveled_record_value_edge, record_value_edge}; use core_types::{CacheHash, Color, ContextModification, MemoHash, Type, TypeDescriptor}; use dyn_any::DynAny; pub use dyn_any::StaticType; @@ -236,11 +236,19 @@ macro_rules! tagged_value { // MANUAL VARIANTS // =============== Self::None => concrete!(()), - Self::TypeDefault(td) => Type::Concrete(td.clone()), - Self::F64Array(_) => concrete!(List), - Self::Color(_) => concrete!(List), - Self::Gradient(_) => concrete!(List), - Self::BrushStrokes(_) => concrete!(List), + Self::TypeDefault(td) => { + let name = td.name.as_ref(); + if name == std::any::type_name::>() { return core_types::registry::record_type::(); } + if name == std::any::type_name::>() { return core_types::registry::record_type::(); } + if name == std::any::type_name::>>() { return core_types::registry::record_type::>(); } + if name == std::any::type_name::>() { return core_types::registry::record_type::(); } + if name == std::any::type_name::>() { return core_types::registry::record_type::(); } + Type::Concrete(td.clone()) + } + Self::F64Array(_) => core_types::registry::record_type::(), + Self::Color(_) => core_types::registry::record_type::(), + Self::Gradient(_) => core_types::registry::record_type::(), + Self::BrushStrokes(_) => core_types::registry::record_type::(), // ======================= // AUTO-GENERATED VARIANTS // ======================= @@ -257,32 +265,41 @@ macro_rules! tagged_value { } } - /// `None` for a [`Self::TypeDefault`] whose named type is outside `for_each_type_default!`. - pub fn element_write(&self) -> Option { - Some(match self { - Self::None => core_types::record::element_write::<()>(), + /// The record layout of this value's edge: leveled for the list-carrying + /// variants, element-only at rank 0 otherwise. `None` for a + /// [`Self::TypeDefault`] whose named type is outside `for_each_type_default!`. + pub fn value_layout(&self) -> Option { + fn leveled() -> Option { + Some(core_types::record::Layout::default().with_writes(1, core_types::record::element_write_hashed::(), &[])) + } + fn scalar() -> Option { + Some(core_types::record::Layout::default().with_writes(0, core_types::record::element_write::(), &[])) + } + match self { + Self::None => scalar::<()>(), Self::TypeDefault(td) => { let name = td.name.as_ref(); - macro_rules! check { - ($type_default:ty) => { - if name == std::any::type_name::<$type_default>() { return Some(core_types::record::element_write::<$type_default>()); } - }; - } - for_each_type_default!(check); - return None; + if name == std::any::type_name::>() { return leveled::(); } + if name == std::any::type_name::>() { return leveled::(); } + if name == std::any::type_name::>>() { return leveled::>(); } + if name == std::any::type_name::>() { return leveled::(); } + if name == std::any::type_name::>() { return leveled::(); } + if name == std::any::type_name::() { return scalar::(); } + if name == std::any::type_name::() { return scalar::(); } + None } - Self::F64Array(_) => core_types::record::element_write::>(), - Self::Color(_) => core_types::record::element_write::>(), - Self::Gradient(_) => core_types::record::element_write::>(), - Self::BrushStrokes(_) => core_types::record::element_write::>(), - $( Self::$identifier(_) => core_types::record::element_write::<$ty>(), )* - Self::RenderOutput(_) => core_types::record::element_write::(), - Self::NodeIdPath(_) => core_types::record::element_write::>(), - Self::DocumentNode(_) => core_types::record::element_write::(), - Self::ContextModification(_) => core_types::record::element_write::(), - Self::EditorApi(_) => core_types::record::element_write::>(), - Self::ResourceHash(_) => core_types::record::element_write::(), - }) + Self::F64Array(_) => leveled::(), + Self::Color(_) => leveled::(), + Self::Gradient(_) => leveled::(), + Self::BrushStrokes(_) => leveled::(), + $( Self::$identifier(_) => scalar::<$ty>(), )* + Self::RenderOutput(_) => scalar::(), + Self::NodeIdPath(_) => scalar::>(), + Self::DocumentNode(_) => scalar::(), + Self::ContextModification(_) => scalar::(), + Self::EditorApi(_) => scalar::>(), + Self::ResourceHash(_) => scalar::(), + } } /// Materializes the value as [`Self::to_dynany`] does, wrapped in a `ClonedNode` edge typed by [`Self::ty`]. @@ -293,29 +310,29 @@ macro_rules! tagged_value { // =============== Self::None => Ok(record_value_edge(())), Self::TypeDefault(td) => { - // Same direct-construction path as `to_dynany` for the same reason as in `to_dynany`. let name = td.name.as_ref(); - macro_rules! check { - ($type_default:ty) => { - if name == std::any::type_name::<$type_default>() { return Ok(record_value_edge(<$type_default>::default())); } + // The list-typed defaults serve an empty level; the rest construct + // their default directly, mirroring `to_dynany`'s recursion guard. + macro_rules! check_level { + ($list:ty, $element:ty) => { + if name == std::any::type_name::<$list>() { + return Ok(leveled_record_value_edge(Vec::<$element>::new())); + } }; } - for_each_type_default!(check); + check_level!(List, Graphic); + check_level!(List, Artboard); + check_level!(List>, Raster); + check_level!(List, Vector); + check_level!(List, String); + if name == std::any::type_name::() { return Ok(record_value_edge(DocumentNode::default())); } + if name == std::any::type_name::() { return Ok(record_value_edge(Resource::default())); } Self::from_type_or_none(&Type::Concrete(td)).to_edge() } - Self::F64Array(values) => { - let list: List = values.into_iter().map(core_types::list::Item::new_from_element).collect(); - Ok(record_value_edge(list)) - } - Self::Color(color) => { - let list: List = color.into_iter().map(core_types::list::Item::new_from_element).collect(); - Ok(record_value_edge(list)) - } - Self::Gradient(stops) => Ok(record_value_edge(List::::new_from_element(stops))), - Self::BrushStrokes(strokes) => { - let list: List = strokes.into_iter().map(core_types::list::Item::new_from_element).collect(); - Ok(record_value_edge(list)) - } + Self::F64Array(values) => Ok(leveled_record_value_edge(values)), + Self::Color(color) => Ok(leveled_record_value_edge(color.into_iter().collect::>())), + Self::Gradient(stops) => Ok(leveled_record_value_edge(vec![stops])), + Self::BrushStrokes(strokes) => Ok(leveled_record_value_edge(strokes)), // ======================= // AUTO-GENERATED VARIANTS // ======================= @@ -587,37 +604,6 @@ tagged_value! { } impl TaggedValue { - /// The flip form of [`Self::to_edge`]: list-carrying variants serve their - /// payload as a level, one lane per item, instead of materializing a - /// legacy list value. Every other variant takes the [`Self::to_edge`] - /// path unchanged. Dormant until the flip retypes the list-typed inputs. - pub fn to_leveled_edge(self) -> Result { - use core_types::value::leveled_record_value_edge; - match self { - Self::TypeDefault(td) => { - let name = td.name.as_ref(); - macro_rules! check_level { - ($list:ty, $element:ty) => { - if name == std::any::type_name::<$list>() { - return Ok(leveled_record_value_edge(Vec::<$element>::new())); - } - }; - } - check_level!(List, Graphic); - check_level!(List, Artboard); - check_level!(List>, Raster); - check_level!(List, Vector); - check_level!(List, String); - Self::TypeDefault(td).to_edge() - } - Self::F64Array(values) => Ok(leveled_record_value_edge(values)), - Self::Color(color) => Ok(leveled_record_value_edge(color.into_iter().collect::>())), - Self::Gradient(stops) => Ok(leveled_record_value_edge(vec![stops])), - Self::BrushStrokes(strokes) => Ok(leveled_record_value_edge(strokes)), - other => other.to_edge(), - } - } - pub fn to_primitive_string(&self) -> String { match self { TaggedValue::None => "()".to_string(), @@ -950,23 +936,37 @@ mod leveled_edges { #[test] fn list_variants_produce_leveled_edges_typed_by_element() { - let edge = TaggedValue::F64Array(vec![1., 2., 3.]).to_leveled_edge().unwrap(); + let edge = TaggedValue::F64Array(vec![1., 2., 3.]).to_edge().unwrap(); assert_eq!(edge.ty(), &record_edge_type::()); assert_eq!(edge.layout().depth, 1); - let edge = TaggedValue::Color(Some(Color::default())).to_leveled_edge().unwrap(); + let edge = TaggedValue::Color(Some(Color::default())).to_edge().unwrap(); assert_eq!(edge.ty(), &record_edge_type::()); assert_eq!(edge.layout().depth, 1); - let edge = TaggedValue::TypeDefault(descriptor!(List)).to_leveled_edge().unwrap(); + let edge = TaggedValue::TypeDefault(descriptor!(List)).to_edge().unwrap(); assert_eq!(edge.ty(), &record_edge_type::()); assert_eq!(edge.layout().depth, 1); } #[test] fn scalar_variants_keep_their_rank_zero_edges() { - let edge = TaggedValue::Bool(true).to_leveled_edge().unwrap(); + let edge = TaggedValue::Bool(true).to_edge().unwrap(); assert_eq!(edge.ty(), &record_edge_type::()); assert_eq!(edge.layout().depth, 0); } + + #[test] + fn the_value_layout_matches_the_edge_layout() { + for value in [ + TaggedValue::F64Array(vec![1.]), + TaggedValue::Bool(true), + TaggedValue::TypeDefault(descriptor!(List)), + TaggedValue::Gradient(Default::default()), + ] { + let layout = value.value_layout().unwrap(); + let edge = value.to_edge().unwrap(); + assert_eq!(&layout, edge.layout()); + } + } } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index ec61b9f151..010b60ca3f 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -376,13 +376,10 @@ impl ProtoNetwork { let layout = { let node = &self.nodes[index].1; match &node.construction_args { - ConstructionArgs::Value(value) => value.element_write().map(|element| { - let layout = core_types::record::Layout::default().with_writes(0, element, &[]); - core_types::record::RecordLayout { - frame_bytes: layout.frame_bytes(), - plan: Vec::new(), - layout, - } + ConstructionArgs::Value(value) => value.value_layout().map(|layout| core_types::record::RecordLayout { + frame_bytes: layout.frame_bytes(), + plan: Vec::new(), + layout, }), ConstructionArgs::Nodes(inputs) => node.resolved.layout_meta.as_ref().and_then(|meta| { let input_layouts: Vec> =