Skip to content

Commit 6c35ab1

Browse files
authored
Merge pull request #100 from VEZY/reparent-and-rechildren-update-parent-and-children
Fix issue #95
2 parents d71b4e6 + 84277ac commit 6c35ab1

6 files changed

Lines changed: 95 additions & 12 deletions

File tree

src/compute_MTG/delete_nodes.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ function delete_node!(node::Node{N,A}; child_link_fun=new_child_link) where {N<:
143143

144144
if !isleaf(node)
145145
# We re-parent the children to the parent of the node.
146-
for chnode in children(node)
146+
for chnode in copy(children(node))
147147
# Updating the link of the children:
148148
link!(chnode, child_link_fun(chnode))
149149
addchild!(parent_node, chnode; force=true)

src/compute_MTG/node_funs.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,11 @@ end
129129
function addchild!(p::Node{N,A}, child::Node; force=false) where {N<:AbstractNodeMTG,A}
130130
child_was_root = parent(child) === nothing
131131

132-
if child_was_root || force == true
133-
reparent!(child, p)
134-
elseif parent(child) != p && force == false
132+
if !child_was_root && parent(child) !== p && force == false
135133
error("The node already has a parent. Hint: use `force=true` if needed.")
136134
end
137135

138-
push!(children(p), child)
136+
reparent!(child, p)
139137
_maybe_recolumnarize_after_attach!(p, child, child_was_root)
140138

141139
return child

src/types/Node.jl

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,25 +175,75 @@ AbstractTrees.parent(node::Node{T,A}) where {T,A} = Base.parent(node)
175175
AbstractTrees.childrentype(node::Node{T,A}) where {T,A} = Vector{Node{T,A}}
176176
AbstractTrees.childtype(::Type{Node{T,A}}) where {T,A} = Node{T,A}
177177

178+
@inline function _child_index_by_identity(chnodes, child)
179+
@inbounds for i in eachindex(chnodes)
180+
chnodes[i] === child && return i
181+
end
182+
return nothing
183+
end
184+
185+
function _detach_child!(p::Node, child::Node)
186+
chnodes = children(p)
187+
removed = false
188+
for i in reverse(eachindex(chnodes))
189+
if chnodes[i] === child
190+
deleteat!(chnodes, i)
191+
removed = true
192+
end
193+
end
194+
removed && _mark_structure_mutation!(p)
195+
return removed
196+
end
197+
198+
function _attach_child!(p::Node, child::Node)
199+
chnodes = children(p)
200+
_child_index_by_identity(chnodes, child) !== nothing && return false
201+
push!(chnodes, child)
202+
_mark_structure_mutation!(p)
203+
return true
204+
end
205+
178206
"""
179207
reparent!(node::N, p::N) where N<:Node{T,A}
180208
181-
Set the parent of the node.
209+
Set the parent of the node, removing it from the old parent's children and adding it
210+
to the new parent's children.
182211
"""
183212
function reparent!(node::N, p::N2) where {N<:Node{T,A},N2<:Union{Nothing,Node{T,A}}} where {T,A}
213+
p === node && error("A node cannot be its own parent.")
214+
215+
old_parent = parent(node)
216+
changed = old_parent !== p
217+
if old_parent !== nothing && changed
218+
_detach_child!(old_parent, node)
219+
end
220+
184221
setfield!(node, :parent, p)
185-
_mark_structure_mutation!(node)
186-
p === nothing || _mark_structure_mutation!(p)
222+
attached = p === nothing ? false : _attach_child!(p, node)
223+
(changed || attached) && _mark_structure_mutation!(node)
187224
return p
188225
end
189226

190227
"""
191228
rechildren!(node::Node{T,A}, chnodes::Vector{Node{T,A}}) where {T,A}
192229
193-
Set the children of the node.
230+
Set the children of the node, detaching removed children and setting this node as the
231+
parent of the new children.
194232
"""
195233
function rechildren!(node::Node{T,A}, chnodes::Vector{Node{T,A}}) where {T,A}
234+
old_children = children(node)
196235
setfield!(node, :children, chnodes)
236+
237+
for old_child in old_children
238+
if parent(old_child) === node && _child_index_by_identity(chnodes, old_child) === nothing
239+
reparent!(old_child, nothing)
240+
end
241+
end
242+
243+
for child in chnodes
244+
reparent!(child, node)
245+
end
246+
197247
_mark_structure_mutation!(node)
198248
return chnodes
199249
end

test/test-descendants.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ end
7272
mtg_b = read_mtg("files/simple_plant.mtg")
7373
# Bypass addchild! on purpose to build an incoherent tree (mixed stores).
7474
reparent!(mtg_b, mtg_a)
75-
push!(children(mtg_a), mtg_b)
7675

7776
err = try
7877
descendants(mtg_a, :Width)

test/test-nodes.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,40 @@ end
8585
@test children(mtg) == [internode]
8686
end
8787

88+
@testset "parent and children setters stay synchronized" begin
89+
mtg = MultiScaleTreeGraph.Node(MultiScaleTreeGraph.NodeMTG(:/, :Plant, 1, 1))
90+
first_parent = MultiScaleTreeGraph.Node(mtg, MultiScaleTreeGraph.NodeMTG(:/, :Axis, 1, 2))
91+
second_parent = MultiScaleTreeGraph.Node(mtg, MultiScaleTreeGraph.NodeMTG(:+, :Axis, 2, 2))
92+
child = MultiScaleTreeGraph.Node(first_parent, MultiScaleTreeGraph.NodeMTG(:/, :Internode, 1, 3))
93+
# Simulate a stale duplicate child entry from direct children vector mutation.
94+
push!(children(first_parent), child)
95+
96+
reparent!(child, second_parent)
97+
@test parent(child) === second_parent
98+
@test !any(n -> n === child, children(first_parent))
99+
@test count(n -> n === child, children(second_parent)) == 1
100+
101+
reparent!(child, second_parent)
102+
@test count(n -> n === child, children(second_parent)) == 1
103+
104+
reparent!(child, nothing)
105+
@test parent(child) === nothing
106+
@test !any(n -> n === child, children(second_parent))
107+
108+
rechildren!(first_parent, typeof(children(first_parent))([child]))
109+
@test parent(child) === first_parent
110+
@test children(first_parent) == [child]
111+
112+
rechildren!(second_parent, typeof(children(second_parent))([child]))
113+
@test parent(child) === second_parent
114+
@test !any(n -> n === child, children(first_parent))
115+
@test children(second_parent) == [child]
116+
117+
rechildren!(second_parent, typeof(children(second_parent))())
118+
@test parent(child) === nothing
119+
@test isempty(children(second_parent))
120+
end
121+
88122
# From a file:
89123
file = "files/simple_plant.mtg"
90124
mtg = read_mtg(file)

test/test-read_mtg.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ end
3535
@testset "test mtg mutation" begin
3636
@test (mtg[:scales] .= [0, 1, 2, 3, 4]) == [0, 1, 2, 3, 4]
3737
@test MultiScaleTreeGraph.node_mtg!(mtg, MultiScaleTreeGraph.NodeMTG(:<, :Leaf, 2, 0)) == MultiScaleTreeGraph.NodeMTG(:<, :Leaf, 2, 0)
38-
reparent!(mtg[1], nothing)
39-
@test parent(mtg[1]) === nothing
38+
child = mtg[1]
39+
reparent!(child, nothing)
40+
@test parent(child) === nothing
41+
@test isempty(children(mtg))
4042
end
4143

4244
@testset "test mtg with empty lines" begin

0 commit comments

Comments
 (0)