Search Unity

Discussion Infinite Loop

Discussion in 'Scripting' started by StarBornMoonBeam, May 3, 2023.

  1. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    I take everyones fav
    Code (CSharp):
    1. for (int i = 0; i < LAYOUT_GROUP.childCount; i++)
    2. {
    3.   Destroy(LAYOUT_GROUP.GetChild(0).gameObject);
    4. }
    I tried something a little different

    Code (CSharp):
    1. for (int i = 0; LAYOUT_GROUP.childCount > 0; i++)
    2. {
    3.   Destroy(LAYOUT_GROUP.GetChild(0).gameObject);
    4. }
    but it causes infinite loops


    Obviously method 1 is the method to use, but I thought as I could break from a for loop from inside of a function that the for-loop launched, by re-allocating the memory of a private list that the for loop compares with (in place where child-count is used above) So I assumed, a for-loop asks every round what is the current layout groups child count but it doesn't, in fact it only knows at the beginning. And then infinitely compares to its starting value. Which is confusing because I thought that sometimes you couldn't remove element from a list while iterating, so you get a debug console about it and an automatic unprogrammed break. Though I haven't seen that since I was starter.

    It seems a little contradictive; It uses the direct reference to the memory of the list, but also uses only the count of that list as it was before its first iteration. !

    Anyways.

    I thought it's interesting;
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,930
    Objects aren't actually destroyed until the end of frame, so yes, your second loop will go on forever because the list is never actually getting smaller. Calling
    Destroy
    on an object just flags it to be cleaned up at end of frame.

    Transform
    implements
    IEnumerable
    , so you can just
    foreach (Transform child in transform)
    to iterate through all children of a given transform.

    Which is say your assumption in your last big paragraph is wrong.
     
    Last edited: May 3, 2023
    Bunny83, StarBornMoonBeam and Yoreki like this.
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,497
  4. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    So it does check that value a new each iter. But the object isn't registered as -1 until

    A crazy old world we live in.
     
  5. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    Code (CSharp):
    1.  
    2. int A = WHITE_BIN.childCount;
    3. int B = BLACK_BIN.childCount;
    4. Debug.Log(WHITE_BIN.childCount);
    5. Debug.Log(BLACK_BIN.childCount);
    6. for (int i = 0; i <  A; i++)
    7. {
    8.      Destroy(WHITE_BIN.GetChild(0).gameObject);
    9. }
    10. for (int i = 0; i < B; i++)
    11. {
    12.      Destroy(BLACK_BIN.GetChild(0).gameObject);
    13. }
    Guys can you take a look at this one. The result only removes one from each bin. I assume because the list is being modified. Same result calling WhiteBin.childcount direct as to calling int A. The value contains 2 in the debug, but the for loop will only destroy 1. So i guess that the child gameobject doesn't destroy until next frame. So a repeated call on destroy object 0 is doing nothing. Am I right?
     
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,004
    Yes, because as Spiney already explained and as explained in the documentation, when you call Destroy on an object, that object is not immediately destroyed but at the end of the current frame. So when you call
    Destroy(xxx.GetChild(0).gameObject)
    you essentially mark the first child for destruction. When you do that 5 times you always just mark the first child for destruction. At the end of the frame the object will actually be destroyed. The best solution is usually this:

    Code (CSharp):
    1. for (int i = WHITE_BIN.childCount-1; i >=0; i--)
    2. {
    3.      Destroy(WHITE_BIN.GetChild(i).gameObject);
    4. }
    However since the destruction is delayed, you can actually walk through the childs forward as well.

    Code (CSharp):
    1. for (int i = 0; i < WHITE_BIN.childCount; i++)
    2. {
    3.      Destroy(WHITE_BIN.GetChild(i).gameObject);
    4. }
    Though in general when destroying items in an array or collection its always safer to go backwards through that collection.

    Just to make that clear: When you call Destroy on a child object, that object is not immediately removed. That means the childCount does not change and the object is still there.

    I probably shouldn't mention it but Unity has the DestroyImmediate method which actually destroys an object immediately. However this is actually meant for editor script usage. At runtime Unity specifically delays the destruction to avoid race conditions between different scripts. DestroyImmediate should not be used in runtime code. Melv already posted the two links to the documentation of those two methods. So I guess you haven't read them, right?
     
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,930
    Yes... I already said that, and the documentation was already linked to.
     
  8. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,074
    Why ?
    Do you mean in unity's case specifically ?
    Or we are assuming there is some kind of list under the hood and we would like to minimize copying ?
     
  9. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    Yes I could have gone backwards.

    But I go forwards with childCount(i)

    Since the list isn't modified until next frame. You can keep iterating through it even though it's pending destroy.

    The reason I even mention it because in comparison to normal list removal procedure it's different because you couldn't use i in a string list removal happening this frame.
     
  10. karliss_coldwild

    karliss_coldwild

    Joined:
    Oct 1, 2020
    Posts:
    602
    Bunnies suggestion applies more in general case, not specific situation with Unity object hierachy. But it has plenty of caveats.

    Naively doing iteration forwards, assuming removal API actually removes and shifts remaining elements, will result in skipping some elements and thus wrong result in certain situations. But that's not a problem in case of destroying unity child objects since destroying will be delayed.

    Iterating backwards and removing produces correct result, but is in worst case is O(n^2). It can be acceptable in certain situations if you know the properaties of data, and list is sufficiently small or you always remove only one or two elements. I would consider blindly repeating "just iterate backwards it's better" without understanding or explaining why harmful. Especially if the API of specif lists don't have the problem which motivates the backward iteration. And you can't ignore the details of how removal API of specific list works, because there are plenty of cases where element removal of elements invalidates iteration regardless of which way you are iterating. So you need think in each case anyway.


    Proper solution for removing some elements of list requires an algorithm that maintains two pointers: read and write.

    Or just use the builtin library function for conditional removal which probably implements the same algorithm. And if you are worried about overhead of lambda call, vs plain iteration, my suggestion is don't. If the list is small it mostly doesn't matter anyway. And if the list turns out to be large, the proper algorithm will still be faster than hacky backwards iteration. Only in the very specific situations this will be worse and those are better discovered by profiling.

    But again both of the last points don't apply to destroying transform child elements.
     
    Bunny83 and StarBornMoonBeam like this.
  11. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    I wouldn't jump to a backwards because of the constant request for the last entry subtract 1 or the original entry count subtract i, when the original original iter just uses the 1 iter++. So normally I use remove at zero. So we just use the for loop int to execute data at the point we want and that's all.

    But ultimately child doesn't take 0 it takes i for the hierarchy update. But normally lists take 0 won't take i unless going backwards

    I don't know why I didn't want backwards it would work. I just didn't want backwards ?? Usually I deplete from zero instead of count but if i was -- and started at count take 1..

    It just seems a chaotic to me unless necessary. Int 0 remove 0 which i+
     
  12. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,004
    Maybe I wasn't that clear and I wrongly said "array" :) Though common usecases when you "delete" things from a List or any other dynamic collection is usually that you want to iterate through all elements and based on some condition you may remove an element.. In that case moving forward through the list is a problem since when you remove an element you either skip the following element, or you need some extra logic to keep the track where you are in the list.

    By going backwards you essentially remove items behind you so it does not affect the order of the elements before.the current one.

    But yes, for lists it's also the least efficient way to delete from the front as it has to move all elements down one slot for each remove. So if you want to empty a list completely, removing element 0 until the list is empty turns an O(n) operation into an O(n²). Of course in many usecases it doesn't really matter, but it would not scale well :)
     
    orionsyndrome likes this.
  13. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,074
    ok thanks, I thought it is some specific unity shenanigans.
     
  14. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    You can go forwards subtract 1 from the i on change. I recently did a remove at i and insert at i in the one loop aswell.