Can you help me find why my script runs forever?


#1

Hello, I’m working on a script to fill a wall of bookshelves with books. The problem is that when hit Execute, the script runs indefinitely (at least 15 minutes) and fills my computer’s ram. It worked on the previous version - all I did was refactor, if I remember correctly. Can you help me find why it is doing this?

Here are the two while loops in the script:

#fill shelf
spaceTaken = 0
while(shelfSpaces[c][r] - spaceTaken > .15):
    gui.MessageDialog("going to make a book")
    spaceTaken += makeBook(template,booksNull,c,r,spaceTaken)
    c4d.EventAdd()

and

def makeBook(template,booksNull,c,r,spaceTaken):
    ...
    #calculates previous shelf space taken up to this point
    prevShelvesSpace = 0
    c_temp = 0
    while(c_temp < c):
        prevShelvesSpace += shelfSpaces[c_temp][0]
        prevShelvesSpace += 2.582#width of shelf divider
        c_temp+=1

So the main thing the script does is create an instance, place it on the shelf, and decide how big it should be (which will eventually be randomized). These simple operations shouldn’t take much time or memory, right?
And here is the full code:

import c4d
from c4d import gui

start = c4d.Vector(-724.424, 357.25+2.582/2, -250+2.582/2)
shelfSpaceY = 48.25#-2.582*2

#these two lists are not really relevant:
#this is how i'm representing the layout of all the bookshelves on the wall. they are divided into spaces of 100cm wide, or in one column's case, 48cm wide
shelfSpaces = [[100,100,100,0  ,0  ,0  ,0  ,0  ],
               [100,100,100,100,100,100,100,100],
               [100,100,100,100,100,100,100,100],
               [100,100,100,100,100,100,100,100],
               [48 ,48 ,48 ,48 ,48 ,48 ,48 ,0  ],
               [100,100,100,100,100,100,0  ,0  ],
               [100,100,100,100,100,100,0  ,0  ],
               [100,100,100,100,100,0  ,0  ,0  ]]
# the last three elements of all these lists are not used yet, ignore them
#gaps = [[start z, width z, stack ct, stack height, margin], ...]
null = [0,0,0,0,0]
gaps = [[null,null,null,null,null,null,null,null],
        [[35,30,0,0, 0],[30,40,0,0,0],null,null,[5,30,4,10,10],null,null,null],
        [null,null,[45,10,4,13,1],[10,35,0,0,0],[35,30,0,0,0],[45,30,4,20,2],[15,35,5,33,1],null],
        [[30,15,0,0,0],[7,35,6,30,2],[55,30,7,38,1],null,[0,40,0,0,0],null,[25,35,0,0,0],null],
        [null,[0,25,0,0,0],null,null,null,[0,48-2.582,5,20,14],[0,20,0,0,0,],null],
        [[80,20,0,0,0],[75,25,0,0,0],[55,20,0,0,0],[35,30,0,0,0],[40,10,0,0,0],null,null,null],
        [null,null,[65,35,0,0,0],[0,45,7,35,10],[75,25,0,0,0],[65,45,0,0,0],null,null],
        [null,null,null,null,[50,47,0,0,0],[15,40,0,0,0],null,null]]

def main():
    #important:
    template = doc.SearchObject("book template")

    booksNull = c4d.BaseObject(c4d.Onull)
    booksNull.InsertUnder(template)

    gui.MessageDialog("initial stuff done")

    for c in range(0,8):
        for r in range(0,8):
            if(shelfSpaces[c][r] != 0):

                #fill shelf
                spaceTaken = 0
                while(shelfSpaces[c][r] - spaceTaken > .15):
                    spaceTaken += makeBook(template,booksNull,c,r,spaceTaken)
                    c4d.EventAdd()

def makeBook(template,booksNull,c,r,spaceTaken):
    #make book
    book = c4d.BaseObject(c4d.Oinstance)
    book[c4d.INSTANCEOBJECT_LINK] = template
    #book = template.GetClone()

    #calculates previous shelf space taken up to this point
    prevShelvesSpace = 0
    c_temp = 0
    while(c_temp < c):
        prevShelvesSpace += shelfSpaces[c_temp][0]
        prevShelvesSpace += 2.582#width of shelf divider
        c_temp+=1

    #put in position
    book.SetAbsPos(c4d.Vector( start[0], (start[1] - shelfSpaceY * r), (start[2] + prevShelvesSpace + spaceTaken) ))

    #if approaching gap
    if shelfSpaces[c][r]-gaps[c][r][0] - spaceTaken < .25:
        thickness = (shelfSpaces[c][r]-gaps[c][r][0]-spaceTaken)

    elif shelfSpaces[c][r]-gaps[c][r][0] - spaceTaken < 1:
        thickness = (shelfSpaces[c][r]-gaps[c][r][0]-spaceTaken)/2
    else:
        #if up against shelf
        if shelfSpaces[c][r]-spaceTaken < 1.25:
            thickness = (shelfSpaces[c][r]-spaceTaken)
        else:
            thickness = 2

    #randomize length
    length = 1

    book.SetAbsScale(c4d.Vector(length,length,thickness))

    #randomize color/apply random material from layer?
    #

    #insert into scene and update space taken
    book.InsertUnder(booksNull)

    return 3.936*thickness

# Execute main()
if __name__=='__main__':
    doc.StartUndo()
    main()
    c4d.EventAdd()
    doc.EndUndo()

Thanks for looking at my problem.


#2

Rather than providing a quick answer, I’d like to demonstrate, how to debug such situation.

First question: How is it possible for the script to run forever?
a) There are only two while loops, which could go nuts.
b) It creates so many objects/instances, C4D is taking very long (which only seems like forever) to update after script execution.

The while loop in makeBook, should be ok as c_temp is incremented and should reach the threshold eventually. Assumption, it has to be the other while loop in main().

Because I’m lazy and do not like to restart my C4D, I immediately added another break condition to this while loop before even thinking about pressing the “Execute” button. Just to make sure, it can not run endlessly nor loop too many times. Basically I added and cntBreak > 0 to the while condition, with cntBreak initialized to 100 before the loop and decremented inside the loop. After the loop I print a message, if cntBreak is zero, so I am aware, the loop got interrupted.

First observation, it does not run endlessly. Obviously this while loop somehow is responsible.

Second observation, even when increasing initial value of cntBreak, it still gets interrupted by cntBreak. And indeed there are shelves being created with a huge amount of books.

So, either spaceTaken is not increasing at all, increasing very slowly or (worst case) even decreasing.
Somehow the result of makeBook() has to be flawed.

Next I printed the result of makeBook() if in range -0.001 to 0.001 (actually a check for zero providing some tolerance for rounding errors) and if negative.

Guess what, there are books being generated with a negative space. This is the culprit and from a logic side, most likely wrong. If it leads to an endless loop, because spaceTaken can stay negative or oscillate in a range below threshold or if it just runs very, very long, I didn’t care anymore. I leave it up to you to fix your math. Please don’t take this as an insult, I’m just too lazy. I’m sure, you’ll find the issue in no time.

A general word of warning:
Especially in Python, when editing your code inside of C4D, always have a proper and secure break condition for while loops. You can loose a lot of work, time and code by writing an accidental “while True”. C4D will look up with no more option to save your work.
Hint:
An additional safety net (which at least avoids losing code) can be to work in an external editor (Sublime, Notepad++, UltraEdit, vi, xemacs, Atom, Visual Studio Code, PyCharm,… whatever you like best) and just open the script file in the Script Manager. After changes in the editor, C4D will automatically ask to reload the script.

Your case is also a perfect example, where you could make use of c4dpy
to really debug like a man (all female readers please forgive, I have no doubt women debug even better, as they have lots of practice, due to the need to debug their partners…) without the need for printing to the console.

One more note: Don’t do EventAdd() inside of those loops. There’s no need to, instead do it once afterwards.

Cheers


#3

Wow, this is really helpful!
I will remember to add extra break conditions. And probably check out those other environments. And debug variables rather than host a code staredown…
As for the problem, I ended up just rewriting everything and pretty much got it working now (I just didn’t want to update until I was sure I fixed it). But I wouldn’t have thought to examine the variable in the while loop condition (as funny as that sounds… it’s like the only thing that has to be wrong.)
Well, thanks for the thorough tips and suggested solution. Here is the result of the mostly working script (though for some reason it isn’t filling the smaller column of shelves):


Now to randomize the colors somehow…