@@ -21,7 +21,7 @@ use graphene_std::transform::ScaleType;
2121use graphene_std:: uuid:: NodeId ;
2222use graphene_std:: vector:: graphic_types;
2323use graphene_std:: vector:: style:: { GradientRamp , GradientSpread , PaintOrder , StrokeAlign } ;
24- use std:: collections:: HashMap ;
24+ use std:: collections:: { HashMap , HashSet } ;
2525use std:: f64:: consts:: PI ;
2626use std:: ops:: Range ;
2727
@@ -1634,6 +1634,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
16341634 migrate_node ( node_id, node, network_path, document, reset_node_definitions_on_open) ;
16351635 }
16361636 create_layer_stacks ( document) ;
1637+ follow_segments_turned_on_load ( document) ;
16371638
16381639 // The old geometry-producing "Text" node was split into the current "Text" (`String[]`) -> converter pair, which reuses the same proto
16391640 // identifier. Runs after `migrate_node` normalizes old text nodes to the legacy 13-input layout, distinguished from the current 12-input
@@ -3347,6 +3348,136 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
33473348 Some ( ( ) )
33483349}
33493350
3351+ // TODO: Eventually remove this document upgrade code
3352+ /// Older stored edits are turned around where they chain into paths on load. Edits naming those segments by side (a handle,
3353+ /// an endpoint, or a G1 pair) in the Path nodes downstream of the one that turned them are turned with them, so each stays on
3354+ /// the side it was made on. Segment IDs are only unique within one shape, so nodes not downstream are left alone.
3355+ fn follow_segments_turned_on_load ( document : & mut DocumentMessageHandler ) {
3356+ for ( network_path, node_id, index, input) in modifications_following_turned_segments ( document. network_interface . document_network ( ) ) {
3357+ document. network_interface . set_input ( & InputConnector :: node_at_index ( node_id, index) , input, & network_path) ;
3358+ }
3359+ }
3360+
3361+ // TODO: Eventually remove this document upgrade code
3362+ /// The stored edits to rewrite after loading, each with its network path, node, and input index: those that turned segments,
3363+ /// with their record taken, and those downstream of them, turned to follow. Costs O(nodes + wires) per node that turned any.
3364+ fn modifications_following_turned_segments ( network : & graph_craft:: document:: NodeNetwork ) -> Vec < ( Vec < NodeId > , NodeId , usize , NodeInput ) > {
3365+ type Location = ( Vec < NodeId > , NodeId ) ;
3366+
3367+ // The nodes reading each node's data, wired straight to the node producing it through nested networks' imports and exports
3368+ let mut readers: HashMap < Location , Vec < Location > > = HashMap :: new ( ) ;
3369+ let mut turned_by: Vec < ( Location , HashSet < graphene_std:: vector:: SegmentId > ) > = Vec :: new ( ) ;
3370+ for ( node_id, node, network_path) in network. recursive_nodes ( ) {
3371+ let here = ( network_path. clone ( ) , * node_id) ;
3372+
3373+ for input in & node. inputs {
3374+ if let NodeInput :: Value { tagged_value, .. } = input
3375+ && let TaggedValue :: VectorModification ( modification) = & * * tagged_value
3376+ && !modification. segments_turned_on_load ( ) . is_empty ( )
3377+ {
3378+ turned_by. push ( ( here. clone ( ) , modification. segments_turned_on_load ( ) . clone ( ) ) ) ;
3379+ }
3380+ }
3381+
3382+ // A network node's inputs only pass through to the nodes inside it, which are wired as readers themselves
3383+ if matches ! ( node. implementation, DocumentNodeImplementation :: Network ( _) ) {
3384+ continue ;
3385+ }
3386+ for input in & node. inputs {
3387+ if let Some ( producer) = producing_node ( network, & network_path, input, 0 ) {
3388+ readers. entry ( producer) . or_default ( ) . push ( here. clone ( ) ) ;
3389+ }
3390+ }
3391+ }
3392+ if turned_by. is_empty ( ) {
3393+ return Vec :: new ( ) ;
3394+ }
3395+
3396+ // Each node's turned segments reach the nodes reading its output, then their readers, and so on
3397+ let mut following: HashMap < Location , HashSet < graphene_std:: vector:: SegmentId > > = HashMap :: new ( ) ;
3398+ for ( origin, turned) in & turned_by {
3399+ let ( origin_path, origin_id) = origin;
3400+ let Some ( origin_node) = network. nested_network ( origin_path) . and_then ( |nested| nested. nodes . get ( origin_id) ) else {
3401+ continue ;
3402+ } ;
3403+
3404+ // Where the output comes from: the node itself, or the nodes a network node exports
3405+ let sources: Vec < Location > = match & origin_node. implementation {
3406+ DocumentNodeImplementation :: Network ( nested) => ( 0 ..nested. exports . len ( ) )
3407+ . filter_map ( |output| producing_node ( network, origin_path, & NodeInput :: node ( * origin_id, output) , 0 ) )
3408+ . collect ( ) ,
3409+ _ => vec ! [ origin. clone( ) ] ,
3410+ } ;
3411+
3412+ let mut reached = HashSet :: new ( ) ;
3413+ let mut pending: Vec < Location > = sources. iter ( ) . flat_map ( |source| readers. get ( source) . into_iter ( ) . flatten ( ) . cloned ( ) ) . collect ( ) ;
3414+ while let Some ( location) = pending. pop ( ) {
3415+ if reached. insert ( location. clone ( ) ) {
3416+ pending. extend ( readers. get ( & location) . into_iter ( ) . flatten ( ) . cloned ( ) ) ;
3417+ }
3418+ }
3419+
3420+ // An edit follows when the node holding it, or a node inside it, reads the turned data
3421+ for ( path, node_id) in reached {
3422+ let enclosing = ( 0 ..path. len ( ) ) . map ( |depth| ( path[ ..depth] . to_vec ( ) , path[ depth] ) ) ;
3423+ for holder in enclosing. chain ( [ ( path. clone ( ) , node_id) ] ) . filter ( |holder| holder != origin) {
3424+ following. entry ( holder) . or_default ( ) . extend ( turned. iter ( ) . copied ( ) ) ;
3425+ }
3426+ }
3427+ }
3428+
3429+ let mut rewritten = Vec :: new ( ) ;
3430+ for ( node_id, node, network_path) in network. recursive_nodes ( ) {
3431+ let location = ( network_path. clone ( ) , * node_id) ;
3432+ let follows = following. get ( & location) ;
3433+
3434+ for ( index, input) in node. inputs . iter ( ) . enumerate ( ) {
3435+ let NodeInput :: Value { tagged_value, exposed } = input else { continue } ;
3436+ let TaggedValue :: VectorModification ( modification) = & * * tagged_value else { continue } ;
3437+ let had_record = !modification. segments_turned_on_load ( ) . is_empty ( ) ;
3438+ if !had_record && follows. is_none ( ) {
3439+ continue ;
3440+ }
3441+
3442+ let mut modification = modification. clone ( ) ;
3443+ modification. take_segments_turned_on_load ( ) ;
3444+ let changed = follows. is_some_and ( |turned| modification. follow_segments_turned_elsewhere ( turned) ) ;
3445+
3446+ if had_record || changed {
3447+ let input = NodeInput :: value ( TaggedValue :: VectorModification ( modification) , * exposed) ;
3448+ rewritten. push ( ( network_path. clone ( ) , * node_id, index, input) ) ;
3449+ }
3450+ }
3451+ }
3452+
3453+ rewritten
3454+ }
3455+
3456+ // TODO: Eventually remove this document upgrade code
3457+ /// The node, not itself a network, producing the data an input reads, looking through the imports and exports of nested networks.
3458+ fn producing_node ( network : & graph_craft:: document:: NodeNetwork , network_path : & [ NodeId ] , input : & NodeInput , depth : usize ) -> Option < ( Vec < NodeId > , NodeId ) > {
3459+ // Nesting is shallow in practice, so this only guards against a malformed document wiring a network back into itself
3460+ if depth > 64 {
3461+ return None ;
3462+ }
3463+
3464+ match input {
3465+ NodeInput :: Node { node_id, output_index } => {
3466+ let node = network. nested_network ( network_path) ?. nodes . get ( node_id) ?;
3467+ match & node. implementation {
3468+ DocumentNodeImplementation :: Network ( nested) => producing_node ( network, & [ network_path, & [ * node_id] ] . concat ( ) , nested. exports . get ( * output_index) ?, depth + 1 ) ,
3469+ _ => Some ( ( network_path. to_vec ( ) , * node_id) ) ,
3470+ }
3471+ }
3472+ NodeInput :: Import { import_index, .. } => {
3473+ let ( parent, parent_path) = network_path. split_last ( ) ?;
3474+ let parent_node = network. nested_network ( parent_path) ?. nodes . get ( parent) ?;
3475+ producing_node ( network, parent_path, parent_node. inputs . get ( * import_index) ?, depth + 1 )
3476+ }
3477+ _ => None ,
3478+ }
3479+ }
3480+
33503481/// Ensure layers are positioned as stacks if they are upstream siblings of another layer
33513482fn create_layer_stacks ( document : & mut DocumentMessageHandler ) {
33523483 document. network_interface . load_structure ( ) ;
@@ -4081,4 +4212,115 @@ mod tests {
40814212 panic ! ( "Duplicate entries in `NODE_REPLACEMENTS`: {duplicates:?}" ) ;
40824213 }
40834214 }
4215+
4216+ // Segment IDs repeat across shapes, so a segment turned on load is followed only by the stored edits downstream of the node that turned it
4217+ #[ test]
4218+ fn turned_segments_are_followed_only_downstream ( ) {
4219+ use graph_craft:: document:: NodeNetwork ;
4220+ use graphene_std:: vector:: misc:: HandleId ;
4221+ use graphene_std:: vector:: { SegmentId , VectorModification , VectorModificationType } ;
4222+
4223+ let segment = SegmentId :: generate ( ) ;
4224+ let turned = VectorModification :: default ( ) . with_segments_turned_on_load ( HashSet :: from ( [ segment] ) ) ;
4225+ let edited_on = |handle : HandleId | {
4226+ let mut modification = VectorModification :: default ( ) ;
4227+ modification. modify ( & VectorModificationType :: SetHandlePosition { handle, relative_position : DVec2 :: X } ) ;
4228+ modification
4229+ } ;
4230+ let value = |modification : VectorModification | NodeInput :: value ( TaggedValue :: VectorModification ( Box :: new ( modification) ) , false ) ;
4231+ let node = |inputs : Vec < NodeInput > | DocumentNode { inputs, ..Default :: default ( ) } ;
4232+
4233+ // One turned Path node feeds another directly, and a second sits inside a nested network whose export feeds a third
4234+ let [ turning, following, unrelated, group, following_group] = [ 1 , 2 , 3 , 4 , 5 ] . map ( NodeId ) ;
4235+ let nested = NodeNetwork {
4236+ exports : vec ! [ NodeInput :: node( turning, 0 ) ] ,
4237+ nodes : [ ( turning, node ( vec ! [ value( turned. clone( ) ) ] ) ) ] . into_iter ( ) . collect ( ) ,
4238+ ..Default :: default ( )
4239+ } ;
4240+ let network = NodeNetwork {
4241+ nodes : [
4242+ ( turning, node ( vec ! [ value( turned) ] ) ) ,
4243+ ( following, node ( vec ! [ NodeInput :: node( turning, 0 ) , value( edited_on( HandleId :: primary( segment) ) ) ] ) ) ,
4244+ ( unrelated, node ( vec ! [ value( edited_on( HandleId :: primary( segment) ) ) ] ) ) ,
4245+ (
4246+ group,
4247+ DocumentNode {
4248+ implementation : DocumentNodeImplementation :: Network ( nested) ,
4249+ ..Default :: default ( )
4250+ } ,
4251+ ) ,
4252+ ( following_group, node ( vec ! [ NodeInput :: node( group, 0 ) , value( edited_on( HandleId :: primary( segment) ) ) ] ) ) ,
4253+ ]
4254+ . into_iter ( )
4255+ . collect ( ) ,
4256+ ..Default :: default ( )
4257+ } ;
4258+
4259+ let rewritten = modifications_following_turned_segments ( & network) ;
4260+ let rewritten_at = |location : ( & [ NodeId ] , NodeId ) | rewritten. iter ( ) . find ( |( path, node_id, ..) | ( path. as_slice ( ) , * node_id) == location) ;
4261+
4262+ assert ! ( rewritten_at( ( & [ ] , turning) ) . is_some( ) , "the record of what turned is taken" ) ;
4263+ assert ! ( rewritten_at( ( & [ group] , turning) ) . is_some( ) , "the nested record of what turned is taken" ) ;
4264+ assert ! ( rewritten_at( ( & [ ] , unrelated) ) . is_none( ) , "an edit not downstream stays as it was" ) ;
4265+
4266+ let expected = value ( edited_on ( HandleId :: end ( segment) ) ) ;
4267+ for downstream in [ following, following_group] {
4268+ let ( .., index, input) = rewritten_at ( ( & [ ] , downstream) ) . expect ( "an edit downstream follows the turn" ) ;
4269+ assert_eq ! ( * index, 1 ) ;
4270+ assert_eq ! ( * input, expected) ;
4271+ }
4272+ }
4273+
4274+ // Inside a network fed by a turned node on one input and something else on another, only the edit reading the turned input follows
4275+ #[ test]
4276+ fn turned_segments_follow_only_the_network_input_they_enter ( ) {
4277+ use graph_craft:: document:: NodeNetwork ;
4278+ use graphene_std:: vector:: misc:: HandleId ;
4279+ use graphene_std:: vector:: { SegmentId , VectorModification , VectorModificationType } ;
4280+
4281+ let segment = SegmentId :: generate ( ) ;
4282+ let turned = VectorModification :: default ( ) . with_segments_turned_on_load ( HashSet :: from ( [ segment] ) ) ;
4283+ let mut edited = VectorModification :: default ( ) ;
4284+ edited. modify ( & VectorModificationType :: SetHandlePosition {
4285+ handle : HandleId :: primary ( segment) ,
4286+ relative_position : DVec2 :: X ,
4287+ } ) ;
4288+ let value = |modification : VectorModification | NodeInput :: value ( TaggedValue :: VectorModification ( Box :: new ( modification) ) , false ) ;
4289+ let import = |import_index : usize | NodeInput :: Import {
4290+ import_type : concrete ! ( ( ) ) ,
4291+ import_index,
4292+ } ;
4293+ let node = |inputs : Vec < NodeInput > | DocumentNode { inputs, ..Default :: default ( ) } ;
4294+
4295+ let [ turning, other, group, reads_turned, reads_other] = [ 1 , 2 , 3 , 4 , 5 ] . map ( NodeId ) ;
4296+ let nested = NodeNetwork {
4297+ exports : vec ! [ NodeInput :: node( reads_turned, 0 ) ] ,
4298+ nodes : [ ( reads_turned, node ( vec ! [ import( 0 ) , value( edited. clone( ) ) ] ) ) , ( reads_other, node ( vec ! [ import( 1 ) , value( edited) ] ) ) ]
4299+ . into_iter ( )
4300+ . collect ( ) ,
4301+ ..Default :: default ( )
4302+ } ;
4303+ let network = NodeNetwork {
4304+ nodes : [
4305+ ( turning, node ( vec ! [ value( turned) ] ) ) ,
4306+ ( other, node ( Vec :: new ( ) ) ) ,
4307+ (
4308+ group,
4309+ DocumentNode {
4310+ inputs : vec ! [ NodeInput :: node( turning, 0 ) , NodeInput :: node( other, 0 ) ] ,
4311+ implementation : DocumentNodeImplementation :: Network ( nested) ,
4312+ ..Default :: default ( )
4313+ } ,
4314+ ) ,
4315+ ]
4316+ . into_iter ( )
4317+ . collect ( ) ,
4318+ ..Default :: default ( )
4319+ } ;
4320+
4321+ let rewritten = modifications_following_turned_segments ( & network) ;
4322+ let rewritten_at = |node_id : NodeId | rewritten. iter ( ) . any ( |( path, rewritten_id, ..) | path. as_slice ( ) == [ group] && * rewritten_id == node_id) ;
4323+ assert ! ( rewritten_at( reads_turned) ) ;
4324+ assert ! ( !rewritten_at( reads_other) ) ;
4325+ }
40844326}
0 commit comments