Search Unity

Working with Bounds

Discussion in 'Scripting' started by gambr, Oct 15, 2019.

  1. gambr

    gambr

    Joined:
    Oct 13, 2017
    Posts:
    109
    Dear All,
    I'm not a C# expert so the answer may be trivial.
    I have a List<Bounds>. If I call Encapsulate directly on an element of the List it does not work. While if I assign the item to a local variable then the Encapsulate works fine. Why?

    Example:

    Code (CSharp):
    1. List<Bounds> bounds = new List<Bounds>();
    2.  
    3. Bounds b1 = new Bounds(new Vector3(), new Vector3(2,0,0));
    4. bounds.Add(b1);
    5. Bounds b2 = new Bounds(new Vector3(1,0,0), new Vector3(2,0,0));
    6. bounds[0].Encapsulate(b2); // here bounds[0] has not changed
    7.  
    8. Bounds debug = bounds[0];
    9. debug.Encapsulate(b2); // here debug has been increased correctly
     
  2. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Did you actually check if bounds[0] changed, or did you check if b1 changed? Bounds are structs and structs are value types, so they get copied by value, not by reference, meaning b1 and bounds[0] contain the same data, but are not the same - thus changing bounds[0] does not change b1 and vice versa.
    So how did you check if the size changed? That's not in your example.
     
  3. gambr

    gambr

    Joined:
    Oct 13, 2017
    Posts:
    109
    OK thanks, I didn't realize that Bounds is a struct not a class.
    I checked the value of bounds[0] with the debugger, after "bounds[0].Encapsulate(b2);" line. Anyway I guess the point is that Bounds is a struct.
     
  4. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    Ich you checked bounds[0] after Encapsulate() it should have changed tho.
     
  5. xCyborg

    xCyborg

    Joined:
    Oct 4, 2010
    Posts:
    633
    I experimented a little w/ this issue: It turns out when indexing a List I couldn't change the value of the item, I had to use and temp variable and reassign it to the List.
    But using a System.Array worked just fine.
    Also I couldn't explicitly get a reference from List with ref.
    That's weird...
    Code (CSharp):
    1. public class Boundings : MonoBehaviour
    2. {
    3.     public List<Bounds> bounds; // System.Collections.Generic.List<T>
    4.     public Bounds b1;
    5.     public Bounds b2;
    6.     void Start()
    7.     {
    8.         bounds = new List<Bounds>();
    9.         b1 = new Bounds(new Vector3(), new Vector3(2, 1, 1));
    10.         b2 = new Bounds(new Vector3(1, 0, 0), new Vector3(2, 1, 1));
    11.         bounds.Add(b1);
    12.     }
    13.     void Update()
    14.     {
    15.         if (Keyboard.current.bKey.wasPressedThisFrame)
    16.         {
    17.             bounds[0].Encapsulate(b2); // This WON'T work
    18.  
    19.             b1.Encapsulate(b2);
    20.             bounds[0] = b1;            // This WORKS
    21.         }
    22.     }
    23.     private void OnDrawGizmos()
    24.     {
    25.         Gizmos.color = Color.red;
    26.         if(bounds.Count>0)
    27.         Gizmos.DrawWireCube(bounds[0].center, bounds[0].size);    
    28.     }
    29. }
    Code (CSharp):
    1.  
    2. public class Boundings : MonoBehaviour
    3. {
    4.     public Bounds[] bounds;  // System.Array
    5.     public Bounds b1;
    6.     public Bounds b2;
    7.     void Start()
    8.     {
    9.         bounds = new Bounds[2];
    10.         b1 = new Bounds(new Vector3(), new Vector3(2, 1, 1));
    11.         b2 = new Bounds(new Vector3(1, 0, 0), new Vector3(2, 1, 1));
    12.         bounds[0] = b1;
    13.     }
    14.     void Update()
    15.     {
    16.         if (Keyboard.current.bKey.wasPressedThisFrame)
    17.         {
    18.             bounds[0].Encapsulate(b2); // This WORKS
    19.         }
    20.     }
    21.     private void OnDrawGizmos()
    22.     {
    23.         Gizmos.color = Color.red;
    24.         if(bounds.Length>0)
    25.         Gizmos.DrawWireCube(bounds[0].center, bounds[0].size);    
    26.     }
    27. }
    28.  
     
  6. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    The behaviour you observe is due to how structs work. They are value types and thus copied when they're assigned.

    The example with the array works because you access the value where it's located in memory.

    That's different when you use the indexer of lists and other types with an indexer. In this case, they're basically more like syntax sugar for parameterized getter/setter functions. So you're actually getting a copy of the bounds.
     
    ZO5KmUG6R likes this.
  7. xCyborg

    xCyborg

    Joined:
    Oct 4, 2010
    Posts:
    633
    I know about structs, but I didn't know about Lists.
    But how does that explain that
    Code (CSharp):
    1. bounds[0] = b1;
    works fine.
    You're setting the actual item not a copy of it.

    And isn't Array also a collection class? or is it just the actual CLR representation of T[] like Int32?
     
  8. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Even though one may say an array is like a collection, it's more like a raw type. It also doesn't meet the typical requirements, it cannot resize automatically, it does not have the common members that you'd find on a collection type, i.e. one that implements ICollection.

    When 'bounds' is an array, that'd just write the value (still a copy, because there's no other way) to that location you indexed.
    When 'bounds' is an indexer of any other type, e.g. a list, it'd act like a method. As mentioned earlier, the indexer [ ] can be understood as syntax sugar for getter/setter.
    If you had a setter, let's say void SetItem(int index, Bounds bounds), you would copy the argument just as well.
    Likewise, a getter Bounds GetItem(int index) would return a copy of the value at the supplied index.
     
  9. xCyborg

    xCyborg

    Joined:
    Oct 4, 2010
    Posts:
    633
    I understand that but since collection bounds is a supposed to return a copy how come
    bounds[0] = b1;
    works fine? isn't bounds[0] just a copy of the first item?
    And likewise why when calling a function like
    bounds[0].Encapsulate(b1);
    that's supposed to modify its state, it doesn't work?
    That's my question?
    Why:
    Code (CSharp):
    1.             bounds[0].Encapsulate(b2); // This WON'T work
    2.             b1.Encapsulate(b2);
    3.             bounds[0] = b1;            // This WORKS
    4.  
     
  10. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Like already mentioned, it functions as both, getter and setter. In this case, it doesn't query anything but sets the value.

    When you use encapsulate on the indexed item directly, you're effectively working on a temporary copy. If that is a pure value type, e.g. no hidden references and side effects, this wouldn't do anything useful but it is still valid syntax because it could do something useful in some cases.

    It compiles into a getter which returns a copy and then calls the method on that copy.

    Another example, and that's where the compiler can even tell you this is not allowed and nonsensical, is when you try to assign a field of a temporary value (i.e. no reference)..
    Just do the same, but do not call a method, instead try to assign a field of these bounds. That would be flagged as an error by the compiler, as it only works when the item returned is any sort of reference.