Notifications
Clear all

[Closed] memory issue getelementsusingface

deleted … for revision

It’s the exact method fromthis postin this thread but with your diagnostic scene inserted. For the 50 teapots your code creates I’m seeing the 200 resulting elements as a result.
My machine is a few years old and definitely not the fastest.

Here’s the complete code to run this test, including my detach algorithm

/*
 diagnostic by denisT
  http://forums.cgsociety.org/showpost.php?p=7825173&postcount=9 
 */
 fn fastAttach nodes =
  (
 	 k = 1
 	 attach = polyop.attach
 	 while nodes.count > 1 do
 	 (
 		 k += 1
 		 attach nodes[k-1] nodes[k]
 		 deleteItem nodes k
 		 if k >= nodes.count do k = 1
 	 )
 	 nodes[1]
  )
  
 fn firstBit arr = 
 (
 	local b
 	for n in arr while
 	(
 		b = n
 		false
 	) do ()
 	b
 )
 
 function fn_detachElementsByDeleting arrObject &collection theName:"Klaas"= 
 (
 	/*<FUNCTION>
 	Description
 		detaches elements as separate objects from a poly, retains object name, material and uv coordinates
 		splits the object in half recursively until elements can't be detached anymore
 	Arguments
 		<array> arrObject: the array of objects we're splitting
 		<array by reference> collection: if an object can't be split anymore, add it to this array.
 	Return
 	<FUNCTION>*/
 	
 	--store some methods
 	local polyop_getElementsUsingFace = polyop.getElementsUsingFace
 	local polyop_deleteFaces = polyop.deleteFaces
 	setCommandPanelTaskMode #create
 	
 	with undo off with redraw off
 	(
 		for theNode in arrObject where iskindof theNode Editable_Poly do
 		(
 			local numfaces = polyop.getNumFaces theNode
 			local faceList = #{1..numfaces} --initialize the facelist
 			local arrElementFaceIndices = #() --an array of bitarrays. Each bitarray represents faces of a single element
 			
 			--get all elements, biggest memory issue arises here
 			while NOT facelist.isEmpty do
 			(
 				local elementFaces = polyop_getElementsUsingFace theNode (firstBit faceList)
 				append arrElementFaceIndices elementFaces
 				facelist -= elementFaces
 			)
 			
 			if arrElementFaceIndices.count > 1 then --if the object has more than one element we want to split it
 			(
 				--get half of the elements
 				local halfOfTheElements = #{1..numfaces}
 				for n = 1 to arrElementFaceIndices.count by 2 do halfOfTheElements -= arrElementFaceIndices[n]
 				
 				--create two halves from one object
 				local theHalf = copy theNode
 				polyop_deleteFaces theHalf -halfOfTheElements delIsoVerts:true --delete unneeded faces from theHalf		
 				polyop_deleteFaces theNode halfOfTheElements delIsoVerts:true --delete the processed faces from the original object
 					
 				--set some properties
 				theHalf.name = theName--theNode.name
 				theHalf.wirecolor = random blue yellow
 				theNode.layer.addNode theHalf
 				
 				--recurse
 				fn_detachElementsByDeleting #(theNode,theHalf) &collection theName:theName
 				
 			)else --if there's one element in the object, collect it
 			(
 				converttopoly theNode --idea by polytools3d at least returns the memory taken  http://forums.cgsociety.org/showpost.php?p=7825138&postcount=8 
 				append collection theNode
 			)
 		)
 	)
 )
 
  gc()
  max create mode
  delete objects
  (
 	 count = 50
 	 seed 0
 	 node = fastAttach \
 	 (
 		 for k=1 to count collect 
 		 (
 			 converttopoly (teapot radius:(random 1 10) segments:(random 10 24) pos:(random -[20,20,20] [20,20,20]))
 		 )
 	 )
 	 numfaces = node.numfaces
 	 
 	clearListener()
 	gc()
 	st = timeStamp()
 	mem = heapFree
 	collection = #()
 
 	fn_detachElementsByDeleting #(node) &collection theName:node.name
 
 	format "Time: % ms, memory: %
Faces: %
" (timestamp()-st) (mem-heapfree) numfaces
 	
  )

ok. now i see what is going on.
the advantage you have is because of using deletefaces method with undo off. with undo off after deletefaces the poly makes a simple (geo and topology) update only. which makes everything faster. with undo ON the same method is about two time slower (what actually i was expecting)

well. the code below is what i used. which is very similar to yours but slightly polished during many years of using.

colored in red was just recently added as a result of the performance investigation. but it keeps the function undoable.

here is a complete solution with test scene setup:

fn fastAttach nodes =
(
	k = 1
	attach = polyop.attach
	while nodes.count > 1 do
	(
		k += 1
		attach nodes[k-1] nodes[k]
		deleteItem nodes k
		if k >= nodes.count do k = 1
	)
	nodes[1]
)
fn firstBit arr = 
(
	local b
	for k in arr while (b = k) == 0 do ()
	b
)
mapped fn splitNode node nodes:#() = if iskindof node Editable_Poly do
(
	appendifunique nodes node
	
	local faces = node.faces as bitarray
	local half = node.numfaces/2
	getelementsusingface = polyop.getelementsusingface
	deletefaces = polyop.deletefaces
	elements = #{}
	while elements.numberset < half and (f = firstBit faces) != undefined do 
	(
		ff = getelementsusingface node f
		faces -= ff
		if not faces.isempty do join elements ff
	)
	if not elements.isempty do
	(
		n = copy (converttopoly node) wirecolor:node.wirecolor
		undo off
		(
			deletefaces n -elements 
			deletefaces node elements 
		)		
		splitNode #(node, n) nodes:nodes
	)
	nodes
)


gc()
max create mode
delete objects
(
	count = 30
	seed 0
	submats = 24
	mat = MultiMaterial numsubs:submats
	for m in mat.materiallist do m.diffuse = random orange green 
	node = fastAttach (for k=1 to count collect 
		(
			n = converttopoly (teapot radius:(random 1 10) segments:(random 10 24) pos:(random -[20,20,20] [20,20,20]))
			polyop.setfacematid n #all (random 1 submats)
			n
		)
	)
	node.material = mat
	numfaces = node.numfaces
	
	gc light:on
	format "detaching... %
" numfaces

	t1 = timestamp()
	m1 = heapfree
	nodes = splitNode node --detachElements node
	format "faces:%(%) 
	time:%
	memory:%
" numfaces objects.count (timestamp() - t1) (m1 - heapfree)
	select nodes
)

the main difference you can see that on every splitting call i collect just only a half of elements of a processing node. which in case of nodes made with many small elements gives a real advantage.

2 Replies
(@grabjacket)
Joined: 1 year ago

Posts: 0

Aren’t you collecting the elements of half of the faces in a node? Possibly these faces could get you all elements or only one with some bad luck, isn’t it? Collecting half of the faces doesn’t guarantee half of the elements. Have you ever had issues with this? Or is it not so big a deal?

Anyway, a big thanks for the help and insights, yet again.

(@denist)
Joined: 1 year ago

Posts: 0

there is no any problem there. i’m just trying to collect a half faces in elements. but of course it’s possible the only one element in mesh, or last picked element takes all rest faces. but as you see i double-check this situation.
the idea is very similar to yours: leave a half with source node and another half with a copy. i just spot collecting elements when a half was reached.

Denis,

The place where you put convertopoly doesn’t help too much, and in case of in case of large scenes you might run out of RAM. Try with 150 nodes and watch the system memory.

You would need to gc() to reclaim the memory, otherwise it keeps leaking every time you run the function.

2 Replies
(@denist)
Joined: 1 year ago

Posts: 0

convertopoly is used there to make the function undoable. if the node is already Editable_Poly it shouldn’t take any time or extra memory usage.
i usually call gc light:on after using of any function that needs a memory (or leaks)

(@polytools3d)
Joined: 1 year ago

Posts: 0

Isn’t your test scene using editable_poly? If so, then the place where you put “converttopoly” does exactly the contrary to what you say, it makes the function slower and it leaks memory.
Also, gc light:on is of no help here.

At list on my end.

Or let me rephrase it, is not that the function leaks because of “converttopoly”, but the use of it was supposed to be to prevent the memory leaking, and in the place it is now it is not doing the job.

using of converttopoly doesn’t and can’t prevent any leaking, but it also doesn’t cause extra leaking.
as i said i add it just to make the function undoable. yes, it drops the performance (~15% on my machine), but this is the price we have to pay for ‘undoability’. i couldn’t find a cheaper solution.

i’ve found…

it’s enough to do ‘converttopoly’ just for only fist (source) node… no extra leaking, no slow-down, undoable:

mapped fn splitNode node nodes:#() convert:on = if iskindof node Editable_Poly do
 (
        if convert do converttopoly node
 	appendifunique nodes node
 	
 	local faces = node.faces as bitarray
 	local half = node.numfaces/2
 	getelementsusingface = polyop.getelementsusingface
 	deletefaces = polyop.deletefaces
 	elements = #{}
 	while elements.numberset < half and (f = firstBit faces) != undefined do 
 	(
 		ff = getelementsusingface node f
 		faces -= ff
 		if not faces.isempty do join elements ff
 	)
 	if not elements.isempty do
 	(
 		n = copy node wirecolor:node.wirecolor
 		undo off
 		(
 			deletefaces n -elements 
 			deletefaces node elements 
 		)		
 		splitNode #(node, n) nodes:nodes convert:off
 	)
 	nodes
 )

You’ve found… Finally!

Faster yes, by ±30%, but the memory leaking is still there. The only way to reclaim that memory is by gc(). The optional light:on doesn’t work

Try count=500 or run the code multiple times without gc()

BTW, what do you mean by undoable? If I undo the operation I get a clean scene, it just deletes everything, but does not restore the original node.

the function leaks without ‘converttopoly’ as well. it needs some investigation about what mostly causes that.

undoable… you are running probably ‘complete’ sample. just split it on creation and execution parts, and you will see that the split function is undoable:

gc()
max create mode
delete objects
_node =
(
	count = 30
	seed 0
	submats = 24
	mat = MultiMaterial numsubs:submats
	for m in mat.materiallist do m.diffuse = random orange green 
	node = fastAttach (for k=1 to count collect 
		(
			n = converttopoly (teapot radius:(random 1 10) segments:(random 10 24) pos:(random -[20,20,20] [20,20,20]))
			polyop.setfacematid n #all (random 1 submats)
			n
		)
	)
	node.material = mat
	numfaces = node.numfaces
	
	gc light:on
	format "detaching... %
" numfaces
	node
)
/*
(
	t1 = timestamp()
	m1 = heapfree
	nodes = splitNode _node
	format "faces:%(%) 
	time:%
	memory:%
" numfaces objects.count (timestamp() - t1) (m1 - heapfree)
	select nodes
)
*/
1 Reply
(@polytools3d)
Joined: 1 year ago

Posts: 0

Yes, that was the OP problem, and converttopoly was used to fix it not to cause it, although I might have expressed myself wrong in some previous post, my intention was not to suggest that the memory leaking was caused by convertopoly, but that it was not working in reclaiming the memory, as it does in Klaas code.

Yes Denis, that’s the source of the problem and probably deleting the faces is not doing the job correctly neither.
Perhaps there is another function that forces the editable_poly to update, that also update the collected instances?
Or perhaps a different approach can do the work in a different way.

the main leaking is caused by ‘copy node’ and ‘collect elements’. i don’t think we can do anything with that.

Page 2 / 3