• Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

 Nested array wonkyness, intended or not?

Hyomoto

Member
I posted this as a bug but on further inspection I'm not sure where the intended versus improper behavior is so I probably should have done this first to get some feed back. When you are using nested arrays, there are some troublesome behaviors that I want to say are bugs, but maybe they are working as intended and other things are at fault. Here is a sample script that demonstrates the issue:
Code:
array = [ ]
for ( var _i = 0; _i < 5; _i++ ) { array[ _i ] = [ 0, 1, _i ] }

array2 = [ ];
arrayValue = [ 0, 1, 2 ];
for ( var _i = 0; _i < 5; _i++ ) {
    arrayValue[ 2 ] = _i;
    array2[ _i ] = arrayValue;
 
}
array3 = [ ];
array3 = array_create( 5, [ 0, 1, 2 ] );

array4 = [ ];
array4 = array_create( 5, arrayValue );

show_debug_message( array );
show_debug_message( array2 );
show_debug_message( array3 );
show_debug_message( array4 );
The output is:
Code:
{ { { { 0,1,0 },  },{ { 0,1,1 },  },{ { 0,1,2 },  },{ { 0,1,3 },  },{ { 0,1,4 },  } },  }
{ { { { 0,1,0 },  },{ { 0,1,4 },  },{  },{  },{  } },  }
{ { { { 0,1,2 },  },{  },{  },{  },{  } },  }
{ { { { 0,1,4 },  },{  },{  },{  },{  } },  }
You can see that only the first one works correctly when displayed with show_debug_message. The other three have varying degrees of issues. The interesting part, and the reason I ask if this is intended and where the bugs may actually be at, is what it seems to be doing is adding that variable to the array, which is itself equal to an array. If you check the value of any indice in that array, it will show up as equal to arrayValue. So, all those blank spaces you see in those declared arrays are actually equal to that variable. If you change it, they seem to change with it though as you can see in array2, this isn't always the case. So either the issue is that the arrays aren't being built correctly or the entire array isn't being properly filled with that variable pointer.

I'm fairly certain this is a bug and not the intended behavior, but I figure it's worth asking/demonstrating.
 
Last edited:

rwkay

GameMaker Staff
GameMaker Dev.
Ok - I am taking a wee look at this one as it intrigued me and the bug is actually in the string output as all the missing entries are the same array and the string output is only displaying that array once (due to some code in there to handle recursive arrays, which is not working correctly) under the hood it is all working correctly (though you are not doing what you thing you are doing).

I will update once I have investigated further.

Russell
 

Hyomoto

Member
That sounds great, I look forward to testing the results! Like I said, I think some of this is intended behavior but there is definitely a bug in there somewhere and it's very inconsistent. For another example:
Code:
for ( var _i = 0; _i < 5; _i++ ) {
  var _array = [ ];
  _array[ 0 ] = 0;
  _array[ 1 ] = 1;
  _array[ 2 ] = _i;

  array[ _i ] = _array;

}
Works, while:
Code:
var _array = [ ];

for ( var _i = 0; _i < 5; _i++ ) {
  _array[ 0 ] = 0;
  _array[ 1 ] = 1;
  _array[ 2 ] = _i;

  array[ _i ] = _array;

}
Does not, probably for the same reasons above. However,
Code:
arrayValue = [ 0, 1, 2 ];

for ( var _i = 0; _i < 5; _i++ ) {
  arrayValue[ 2 ] = _i;

  var _array = arrayValue;

  array[ _i ] = _array;

}
Does not. As you said, I'm probably not doing what I think I'm doing but the problem is that the behaviors are very inconsistent and it seems impossible to get the desired output in some cases. Ideally you could define an array as a variable and assign a copy of that array to each index but unless you build the array from scratch as in the second example (including the initial clearing of the array), it just doesn't work. I'm guessing this is related to speeding up the accessing of arrays by not copying them until they are changed.

I just uploaded a .yyz file to my bug on the tracker. Daniel Cleaton is who was assigned to it if that helps at all.
 
Last edited:

rwkay

GameMaker Staff
GameMaker Dev.
OK but the problem is that the strings you are looking at to verify behaviour are incorrect (as in they are printing the wrong thing) if you used the debugger you should see consistent results.

The root of the problem that you are seeing is that in your first example it is correct, in the second one you are changing the same array and inserting that into the primary array (that means each entry is pointing at the same array, and so they are all changing), the third one is doing the same thing as the second one and placing the same array into the primary array.

The bug with string() on the array is that it was only displaying the first occurrence of any array that was inside another and would just print an empty entry for any subsequent occurrence of it.

Russell
 

Hyomoto

Member
That's why I think it's intended behavior, but in the second example the first indice is changing despite the other three being correct. Also, I went back and tested it and the second example ALSO doesn't work. So now it starts getting weird: when using array_create with [ ], because the array being created isn't assigned to a variable, is that destroyed once the script ends? Or what about:
Code:
for ( var _i = 0; _i < 5; _i++ ) {
  var _temp = [ _i ];

  array[ _i ] = _temp;

}
This is just same question, but in this case it's definitely a temporary variable so what happens when the script ends? Is it cleaned up? My point is that while the string output may be wrong, there's some ambiguity here, and as I demonstrated in the .yyz I uploaded, there doesn't seem to be a reliable way to build an array of nested arrays outside of the first method, a literal declaration. That might be by design, but it seems to create some vagaries. I appreciate you looking into this. The expected outcome is if I assign [ ] to a variable, it should create a new array at the very least, but as shown it doesn't and that creates some unexpected behaviors.
 

rwkay

GameMaker Staff
GameMaker Dev.
OK not seen what the YYZ is but in that last example you gave then _temp is being assigned a new array within the loop so it is a new array each time so that is as expected as well.

It sounds like you are getting very confused over something that is very simple

Russell
 

Hyomoto

Member
Well, I made a mistake in the .yyz and so some of it is working as expected. However, there is an issue and it's not in debug_show_message. If I declare an array using a local variable, that variable is gone once script has ended. So, in order to access it I have to copy it to a new variable.
Code:
_temp = array[ 0 ]; _temp[@ 0 ] = 0;
If I do it this way, the array values are PRESERVED, and my change is reflected in all indices that are the same array. If I do this:
Code:
_temp = array[ 0 ]; _temp[ 0 ] = 0; array[ 0 ] = _temp
A new array is created. However, when I do this:
Code:
array2 = [ ];
arrayValue = [ 0, 1, 2 ];
for ( var _i = 0; _i < 5; _i++ ) {
   arrayValue[ 2 ] = _i;
    array2[ _i ] = arrayValue;

}
It creates a new array AND preserves the old array for no apparent reason. Regardless of the intended output, it does NOT properly perform EITHER action ( creation of a new array or preservation of the old one). Hence my bug report, hence my pressing the point. Oh, and let's not forget that if @ is supposed to preserve the array:
Code:
array2 = [ ];
arrayValue = [ 0, 1, 2 ];
for ( var _i = 0; _i < 5; _i++ ) {
  var _temp = arrayValue;
   _temp[ 2 ] = _i;
    array2[ _i ] = _temp;

}
Should create a new array, while:
Code:
array2 = [ ];
arrayValue = [ 0, 1, 2 ];
for ( var _i = 0; _i < 5; _i++ ) {
  var _temp = arrayValue;
   _temp[@ 2 ] = _i;
    array2[ _i ] = _temp;

}
It should preserve the old one, except in both cases it preserves the old array. If we had an accessor for when we ~know~ we want to duplicate the array it might solve some of this since we could simply declare when the array is supposed to be duplicated and therefore know when it is supposed to be preserved. But right now we don't know, and the behavior is inconsistent.
Code:
newArray = @oldArray
 
Last edited:

rwkay

GameMaker Staff
GameMaker Dev.
OK first of all part of your confusion I think is due to the fact that you are thinking that changing an element of an array is creating a new array, this is only true if you have been passed the array and are not the owner in the code fragments you have shown you are the owner of the array so you are changing the array directly not making a new copy.

In GMS2 we have added a new function array_copy that you should use to copy an array (in whole or in part) .

So your confusion I think is in the understanding of ownership of the array... if the array is created in the script then any changes to that array are made directly (or else arrays would be very expensive indeed) but when an array is passed into a function then the copy on write behaviour takes over and you need to use the [@ ] syntax to write through to the underlying array and avoid the copy on write behaviour.

Russell
 

Hyomoto

Member
Not exactly, as I demonstrated this is not the case. Because I've shown an instance where using [@ causes the same output as [. I apologize, for something like this my post needs to be very concise with very specific examples but I'm not used to debugging the language itself, so hopefully my last post demonstrates the ambiguity I'm speaking about properly. The point is, if the array is meant to be preserved we need to know that it's going to in ALL cases, and if it's going to be duplicated we need to know it's going to be in all cases. Right now it doesn't work like that.

I get a bit flummoxed because I promise I'm pointing out an issue, and I hate doing it poorly and being misunderstood. I thank you for your time and patience, but if this is all intended behavior then that's just poorly done. The expected outcome should be the same regardless. If array = array2 is going to preserve the array, it should always do that. There is another case where an accessor would help though it's more minor:
Code:
array_create( array, @[ ] )
Since right now you can't use it to populate unique arrays.
 

Hyomoto

Member
Also, I think you replied to my original crappy response which I apologize for. I put together one that hopefully ties all this together into something useful. Once again, thank you for your time and patience.
 

rwkay

GameMaker Staff
GameMaker Dev.
OK I may be being a bit thick here and not seeing the problem that you are seeing but I will list what is going in arrays

1. a[0] and a[@ 0] work the same way if you are the "owner" of the array - the script that creates the array is the owner, using a[@ 0 ] ensures that the array will not incur a copy when it is written to.
2. a = [ 0, 1, 2, 3 ] will create an array, as will array_create() and a[0] = 0; (when a has not been array before).
3. var a = [0, 1, 2, 3] will declare a as an array and will create the populated array, NOTE: a will be assigned at the location each time so in something like for( var i=0; i<10; ++i) { var a = [0, 1, 2, 3]; .... } then a will be assigned a new array each time through the loop
4. an array can be assigned to multiple variables (or into multiple slots of another array) but all of them will still point to the same array and if the underlying array changes then any of those references will reflect that change.

None of your examples (to me) show any deviation from the above and are working as expected (apart from the bug as noted above with string() that your example uncovered)

Russell
 

Hyomoto

Member
With the sole exception of array2 in my original example, those rules do hold true which is why I think there is an issue going on somewhere. Originally I wasn't sure if the preserving the array was intended, but since it is that sort of answers my question about what is the bug and what is intended. In that example it's performing the same operation five times, but it creates one array, and preserves one array despite the cycle being the same, so now you have one array once, and another array four times. Perhaps something else is going on but that definitely looks like a bug.

I'd like to deviate a moment from the original point of this post if you'll indulge me and ask about that ambiguity in dealing with arrays. As you've noted [ and [@ work the same if the array was created as part of the current script, is there any chance we can get some sort of syntax to ensure the duplication of an array? The use I want is to be able to create a default array, then use that as a template to build the initial layout of a list. The reason I want to use a variable is because being in development, the nature of those defaults may change and I don't want to search for a dozen places it was used that need to be fixed, or if I change the order of the data, or whatever it might be, it would just be useful to update that initial list and my macros. As it is, I have to create a empty array, use a for loop to duplicate it, and then assign that to the list, and then repeat that action each time. This only grows more complicated if I do something like nest another array. Ie: use the functionality available. A drawback of being unable to this is I cannot use the [@ accessor anymore because I have no idea whether the array stored is the default or an original, and therefore HAVE to create a new array each time. That being the case, it would be great if I could duplicate the array literally as an alternative to just assigning the original array just as we have an accessor to ensure we don't.
 

rwkay

GameMaker Staff
GameMaker Dev.
Your array2 does follow those rules, at least when I ran the code here it did - the output on the show_debug_message was incorrect due to the bug in string() (which show_debug_message does for you on the arrays behind the scenes).

if you want to ensure that an array is duplicated then do array_copy( [] , 0, a, 0, array_length_1d(a) ); and that should duplicate the array efficiently for you.

Russell

Edit: fixed name of array_length_1d, always get that the wrong way round...
 
R

renex

Guest
Couldn't the recursion detection print out something to the string?

{0,{<recursion>}}
 

Hyomoto

Member
Alright, that sounds good then. I'll test it out when a new build comes up and if it's dead and buried I'll consider it happily solved.

Also, I guess I don't care for the length of the solution, but as long as there is a way to ensure the duplication occurs, that's a reasonable solution for me. Once again, thanks for putting up with me on this.

It is kind of weird that array_copy doesn't return the result though. I'm guessing that's for speed purposes.
 
Last edited:

FrostyCat

Redemption Seeker
Is it just me, or am I the only person who thinks this should be unit tested? A public repository of basic operator and type handling tests in project form, that anyone can run to check an export/version or report oddities to. It also serves as a standardized source of examples in addition to the Manual.
 
R

renex

Guest
From Mantis comments it sounds like they already have a thing like that for 1.X. They sometimes mention a project called "GMDeath". I'd assume it's a massive project intended to test the limits of the ide and compiler.
 

GMWolf

aka fel666
Is it just me, or am I the only person who thinks this should be unit tested? A public repository of basic operator and type handling tests in project form, that anyone can run to check an export/version or report oddities to. It also serves as a standardized source of examples in addition to the Manual.
Would it not be great if someone built a unit testing framework for GM. perhaps they could call it GMAssert or something ;)
 

Hyomoto

Member
Well, I'm back to this again. Using array_copy doesn't work because it has all the same downfalls minus one. If you have a -single- array, you -can- successfully copy it that way. However, if you do something like this:
Code:
for ( var _x = 0; _x < value; _x++ ) {
    blankArray[ _x ] = [ undefined, 0, c_black, 1, 0 ];
 
}

for ( var _y = 0; _y < value; _y++ ) {
    array[ _y ] = [ ];
    array_copy( array[ _y ], 0, blankArray, 0, value );
 
}
Then you DO get a new array, full of the other arrays and we come full circle to the initial issue all over again. I have no idea if this is simple on your end at all, but I have to again request some sort of accessor or function that will ensure that the -entire- array is duplicated -including- the contents. I'm not sure changing array_copy is a good solution, but maybe it could have a force copy argument added to it since this behavior IS desirable in some cases. It's possible to work around this on my end I'll admit, but it makes nested arrays pretty messy in certain circumstances which is a pretty heavy offset to how useful they are in others. Without chained accessors, their use is already kind of messy so I implore you, give this some consideration!
 
Top