• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GameMaker Need help understanding arrays

D

Drenathor

Guest
Hey guys,

If someone has a resource they can point me towards that will help me understand the way GML handles arrays that would be amazing! Unfortunately right now the more I try to use them the more confused I get.

Here's some code that I'm running as tests along with the output and my notes, questions and comments:

Code:
// test 1
test1 = [[0,1,2,3],[4,5,6,7]];

//show_debug_message( test1 ); // {{{{0,1,2,3},},{{4,5,6,7},}},} // what's with all the extra brackets?!
//show_debug_message( test1[0] ); // {{0,1,2,3},}
//show_debug_message( test1[1,0] ); // !error // why is this an error?

tmp = test1[1];
//show_debug_message( tmp ); // {{4,5,6,7},}
//show_debug_message( tmp[0] ); // 4 // why does this work but calling it directly doesn't?



// test 2
test2[0,0] = 0;
test2[0,1] = 1;
test2[0,2] = 2;
test2[0,3] = 3;
test2[1,0] = 4;
test2[1,1] = 5;
test2[1,2] = 6;
test2[1,3] = 7;

//show_debug_message( test2 ); // {{0,1,2,3},{4,5,6,7},} // Why didn't test1 format this way?
//show_debug_message( test2[0] ); // 0 // Where is all the data?
//show_debug_message( test2[1,0] ); // 4 // Not sure how it's getting the data if test2[1] doesn't...

tmp = test2[1];
//show_debug_message( tmp ); // 1 // where is all my data?
//show_message( tmp[0] ); // error // why is this an error?



// test 3
tmp = [0,1,2,3];
test3[0] = tmp;
tmp = [4,5,6,7];
test3[1] = tmp;

//show_debug_message( test3 ); // {{{{0,1,2,3},},{{4,5,6,7},}},} // wish it would format this similar to test2
//show_debug_message( test3[0] ); // {{0,1,2,3},}
//show_debug_message( test3[1,0] ); // error // again, don't understand the error
//tmp = test3[1]; show_debug_message( tmp[0] ); // 4 // when doing it this way works



// test 4
tmp[0] = 0;
tmp[1] = 1;
tmp[2] = 2;
tmp[3] = 3;
test4[0] = tmp;
tmp[0] = 4;
tmp[1] = 5;
tmp[2] = 6;
tmp[3] = 7;
test4[1] = tmp;

//show_debug_message( test4 ); // {{{{4,5,6,7},},{{4,5,6,7},}},} // Apparently doing it this way as opposed to test3's method uses pointers?! Why?
//show_debug_message( test4[0] ); // {{ 4,5,6,7 },}
//show_debug_message( test4[1,0] ); // error



// test 5
/**
 * I've created a script to essentially do the same thing as above as a workaround to the pointer issue...
 * my_script contains the following code:
 *
 * ---------------------------------
 * var t = [];
 * var j = argument0 * 4;
 *
 * t[0] = 0 + j;
 * t[1] = 1 + j;
 * t[2] = 2 + j;
 * t[3] = 3 + j;
 *
 * test5[argument0] = t;
 * ----------------------------------
 *
 * As you can see it simply does the same thing as test 4 but populates via a script instead of inline.  
 * A horribly convoluted solution if I do say so myself ;)
 */
test5 = [];
my_script(0);
my_script(1);

//show_debug_message( test5 ); // {{{{0,1,2,3},},{{4,5,6,7},}},} // surely there's an easier way to do this right?
Anyways, as you can see different methods of creating arrays and setting values result in different data-set structures and different results / errors when attempting to retrieve them.


Right now my assumptions are that you can't use variable[0,3] structures to retrieve data unless you used that format to store data or you end up with errors. Furthermore if you store data this way you can't pull out the 1d "parent" arrays or you get nonsense data and or errors.

However, if you use the variable = [[0,2],[4,6]] method of storing data you have to retrieve it in multiple steps or you end up with errors using something like variable[1,1].

And I still don't understand all the "extra" brackets in that code output :p

Anyways, thanks for taking the time to look through this mess!
-John
 

TheouAegis

Member
// test 1 test1 = [[0,1,2,3],[4,5,6,7]];
//show_debug_message( test1 ); // {{{{0,1,2,3},},{{4,5,6,7},}},} // what's with all the extra brackets?! //show_debug_message( test1[0] ); // {{0,1,2,3},}
//show_debug_message( test1[1,0] ); // !error // why is this an error?
I haven't messed with shortcut generation, but my guess is in this situation you're actually creating an array of references to additional arrays. So test1[0] is not test[1,0...3], but rather test1[0]=n[0...3]. Hence the extra brackets. This seems to be a logical explanation when you compare the debug messages to test2.

As for why you get an error there, it's because, if as I said is true, then your array is a 1D array with pointers to another 1D array and not a 2D array, so trying to read it as a 2D array causes an error.

tmp = test1[1];
//show_debug_message( tmp ); // {{4,5,6,7},}
//show_debug_message( tmp[0] ); // 4 // why does this work but calling it directly doesn't?
So now here you're setting tmp to the array pointed to in test1[1], which is the set [4,5,6,7]. tmp itself is not the same array as test1, it's just the array stored in test1[1]. So tmp[0] is the first index of the array stored in test1[1].

//show_debug_message( test2 ); // {{0,1,2,3},{4,5,6,7},} // Why didn't test1 format this way?
//show_debug_message( test2[0] ); // 0 // Where is all the data?
//show_debug_message( test2[1,0] ); // 4 // Not sure how it's getting the data if test2[1] doesn't...
Here you set the array up explicitly as a 2D array. Notice the debug message's format contrasted with test1's.

The first call for test2[0] is 0 because when a 2D array is referenced as a 1D array, it is treated as array[n,0]; test2[0,0] = 0. Or at least it should be. If test2[1] causes an error for you (you said it does but your test doesn't show that), then the test2[0] would differ from test2[1] in that it references the base variable.

tmp = test2[1];
//show_debug_message( tmp ); // 1 // where is all my data?
//show_message( tmp[0] ); // error // why is this an error?
Not sure about this one. My only guess is that in a legit 2D array, array[n] is a direct pointer to the specific index. So if you had a 2D array defined as array[0...3, 0...5], it would be a 4x6 array such that array[8] should be a reference to array[1,1]. If that's the case, then that would explain why test2[1] = 1 and why the second debug resulted in an error -- because tmp is not an array.

// test 3
tmp = [0,1,2,3];
test3[0] = tmp;
tmp = [4,5,6,7];
test3[1] = tmp;

//show_debug_message( test3 ); // {{{{0,1,2,3},},{{4,5,6,7},}},} // wish it would format this similar to test2
//show_debug_message( test3[0] ); // {{0,1,2,3},}
//show_debug_message( test3[1,0] ); // error // again, don't understand the error
//tmp = test3[1]; show_debug_message( tmp[0] ); // 4 // when doing it this way works
This test confirms my suspicion about test1. Notice the debug is the same for test3 as it was for test1. In other words, test1 was a 1D array holding references to separate 1D arrays. That is why test3 is not the same format as test2.

The error is because test3 is not a 2D array, it is a 1D array with pointers to a separate array and you were trying to reference it as a 2D array.

What if you tried test3[1][0]?

// test 4
tmp[0] = 0;
tmp[1] = 1;
tmp[2] = 2;
tmp[3] = 3;
test4[0] = tmp;
tmp[0] = 4;
tmp[1] = 5;
tmp[2] = 6;
tmp[3] = 7;
test4[1] = tmp;

//show_debug_message( test4 ); // {{{{4,5,6,7},},{{4,5,6,7},}},} // Apparently doing it this way as opposed to test3's method uses pointers?! Why?
//show_debug_message( test4[0] ); // {{ 4,5,6,7 },}
//show_debug_message( test4[1,0] ); // error
Interesting test. My guess is it's the opposite of what you were saying -- test3 references tmp which holds a pointer to an array, whereas in this test tmp is the array itself and you're setting test4[0] and test4[1] to a pointer to tmp. Thus any read of test4[0] is the same as a read of test4[1] since they both just hold the pointer to the array tmp.

Again, the error is because the arrays shown in the debug do not belong to test4, so test4 is not a 2D array.

The difference between test4 and test5 is in test5 you used a temporary variable which was discarded between indexes. So the value of t when argument0 was 0 is not the same as the value of t when argument0 was 1, hence the two indexes of test5 were two different arrays.


All of this was very interesting and leads me to question if perhaps there may be a memory leak as a result of initializing arrays inline like that.

array[0] = a
array[1] = b
array[2] = c

Here the array [a,b,c] would be the actual data referenced here, so freeing the array would free all the data associated with it?

array = [a,b,c]

Is there a memory leak here, since array is a single variable holding a pointer to an ephemeral array [a,b,c] and not the array itself, thus freeing the value of array would not actually free the set of data, just the pointer? Or is the pointer traced and both the pointer and the array it referenced get freed?
 
D

Drenathor

Guest
Hey @TheouAegis, thanks for the detailed response!

I'm coming primarily from a background in PHP where no matter how I store arrays or retrieve them they are always consistent so this has been something of a steep learning curve for me and has resulted in more than a few moments of "why didn't that work?!" :p

I haven't messed with shortcut generation, but my guess is in this situation you're actually creating an array of references to additional arrays. So test1[0] is not test[1,0...3], but rather test1[0]=n[0...3]. Hence the extra brackets. This seems to be a logical explanation when you compare the debug messages to test2.

As for why you get an error there, it's because, if as I said is true, then your array is a 1D array with pointers to another 1D array and not a 2D array, so trying to read it as a 2D array causes an error.
That's kind of what I was fearing. It would have been nice to be able to use things like the var = [[a,b,c],[d,e,f]] to create arrays quickly but sadly it appears that the only way in GML to create true 2D arrays then is to explicitly write out every single new entry as its own data[0,3] coordinate style entry. Hmmm... I may have to reconsider my tactics and see if this is worth it. I was wondering why GML lacked so many "common" array functions but now I may understand why.

So now here you're setting tmp to the array pointed to in test1[1], which is the set [4,5,6,7]. tmp itself is not the same array as test1, it's just the array stored in test1[1]. So tmp[0] is the first index of the array stored in test1[1].
I knew why that code worked, but I was more curious why it didn't work the other way. I guess I was rather bummed to find out that test1[1][3] isn't available in GML and that test1[1,3] won't work since no array is treated as a 2D array unless manually set up as such. Needing to write out multiple lines of code just to get at the array data is tedious but if that's the only way to retrieve the data from the pointer so be it. I just wish that if you wrote out test[1,3] GML could figure out that this referenced an array and get the 3rd item from it I guess.

Here you set the array up explicitly as a 2D array. Notice the debug message's format contrasted with test1's.

The first call for test2[0] is 0 because when a 2D array is referenced as a 1D array, it is treated as array[n,0]; test2[0,0] = 0. Or at least it should be.
Oh! I missed that info in the documentation... Thanks for that explanation :) I had also tried using test2[1] which returned 1 (although I didn't mention that above I don't think). But I'm guessing that this means it's not possible to get the "parent" array from GML in a true 2D array then huh? So maybe pursuing this route is a waste of time then for me as that's handy functionality to have...

If test2[1] causes an error for you (you said it does but your test doesn't show that), then the test2[0] would differ from test2[1] in that it references the base variable.
Oh sorry, I must not have been clear on that point. test2[1] worked perfectly, but in trying to expose the parent array I had tried setting tmp = test2[1] and then trying to get at tmp[1] (which obviously error-ed since as you pointed out I essentially set tmp = test2[0,1].

This test confirms my suspicion about test1. Notice the debug is the same for test3 as it was for test1. In other words, test1 was a 1D array holding references to separate 1D arrays. That is why test3 is not the same format as test2.

The error is because test3 is not a 2D array, it is a 1D array with pointers to a separate array and you were trying to reference it as a 2D array.

What if you tried test3[1][0]?
Sadly test3[1][0] will not compile as GML doesn't support this type of request ;(

Interesting test. My guess is it's the opposite of what you were saying -- test3 references tmp which holds a pointer to an array, whereas in this test tmp is the array itself and you're setting test4[0] and test4[1] to a pointer to tmp. Thus any read of test4[0] is the same as a read of test4[1] since they both just hold the pointer to the array tmp.

Again, the error is because the arrays shown in the debug do not belong to test4, so test4 is not a 2D array.

The difference between test4 and test5 is in test5 you used a temporary variable which was discarded between indexes. So the value of t when argument0 was 0 is not the same as the value of t when argument0 was 1, hence the two indexes of test5 were two different arrays.
I don't suppose there's a better way to do this then huh? In my case this was completely discovered by accident as I was using a script to populate my array based on argument data. And that worked perfectly until I tried doing something almost identical without the script and it simply returned the last array a bunch of times. Fun times were had trying to debug that LOL.

All of this was very interesting and leads me to question if perhaps there may be a memory leak as a result of initializing arrays inline like that.

array[0] = a
array[1] = b
array[2] = c

Here the array [a,b,c] would be the actual data referenced here, so freeing the array would free all the data associated with it?

array = [a,b,c]

Is there a memory leak here, since array is a single variable holding a pointer to an ephemeral array [a,b,c] and not the array itself, thus freeing the value of array would not actually free the set of data, just the pointer? Or is the pointer traced and both the pointer and the array it referenced get freed?
That's an interesting though. I'm not sure if this is an adequate test or not... but I wrote 2 scripts to attempt to test this...

Script 1
Code:
var hash = "a8wyh4tp8aywhl4eihrlaoiuwehp0a8yw34tp098auwnp0gvtrynuweyvhtnaoiweytva9w8y3nvtp98awyuvnto9a8wy3vo9trywao938r7voa9w837vnp9a8w3yurv98wa3or9v8awy3ov98yw3o987tyw98a34ytp9a84ytvoa94wyvow94yvto98wy43vpt9ytweiotvuyeoiruytvsoiuerytvos98y4vp98ytvp984yvoi3w4utvywo3u4ytvno9348vytpw3948tvyqp348tviwurehvw";
var i = 0;
repeat( 30000 ) {
   memory[i] = hash + hash + hash + hash + hash + string( i );
   i++;
}
Script 2
Code:
var hash = "a8wyh4tp8aywhl4eihrlaoiuwehp0a8yw34tp098auwnp0gvtrynuweyvhtnaoiweytva9w8y3nvtp98awyuvnto9a8wy3vo9trywao938r7voa9w837vnp9a8w3yurv98wa3or9v8awy3ov98yw3o987tyw98a34ytp9a84ytvoa94wyvow94yvto98wy43vpt9ytweiotvuyeoiruytvsoiuerytvos98y4vp98ytvp984yvoi3w4utvywo3u4ytvno9348vytpw3948tvyqp348tviwurehvw";
var i = 0;
var str = "";
repeat( 30000 ) {
   str = str + hash + string(i);
}
memory = [[str + string(1),str + string(2)],[str + string(3),str + string(4),str + string(5)]];
str = "";
I then set it up so that backspace clears the contents of the variable "memory", shift ran script 1 and space bar ran script 2. My results were that script 1 generated 47.9 MB of memory usage and script 2 used 41.9 MB of memory and in each case hitting backspace immediately dropped the memory usage of the app back to around what it was at launch. (although admittedly I've never written a test like this before so it's possible I botched it somewhere and it's a useless test too :p) But my initial impression is that memory leaks in arrays aren't something we need to worry about.
 

Joe Ellis

Member
iv never actually used arrays in most of those ways you've mentioned, so i cant say whether theyre wrong or not, but the way i have used and know works is test2,

the creation of test2 is done correctly, but then tmp is accessing it wrong, test2 is 2d, so accessing it with only one column wont get anything, you'd have to do tmp= test2[i,j]

in test4, your storing the variable name(pointer to) "tmp" in test4[0] & [1], none of the data in the tmp array is being transfered into the test4 indices, just the name,

so really the answer is you need to create arrays how test2 does, and read them in the same way as its created:

Code:
test2[0,0] = 0;
test2[0,1] = 1;
test2[0,2] = 2;
test2[0,3] = 3;
test2[1,0] = 4;
test2[1,1] = 5;
test2[1,2] = 6;
test2[1,3] = 7;

show_debug_message( test2[1,0] ) //this will display message: "4"

tmp = test2[1,2]; //this will set tmp to 6

i hope that helps, if your still confused or i didnt explain it very well just let me know,
and i'm also using gms 1.4, so it might not apply to gms2
 
Last edited:

TheouAegis

Member
The test4/test5 inline workaround would be probably to just declare multiple temporary variables.

There may be a way to use accessors in all this, but i never have used them for arrays outside scripts.

This array behavior comes in part as a result of how Studio handles passing arrays to scripts as arguments. In legacy versions you could not create an array in one line, nor could you pass a full array as an argument for scripts.
 
D

Drenathor

Guest
Hey @321looloo, thanks for your input. Yeah the array = [a,b,c]; method is new to GMS2 so prior to that, the only two methods would have been to either set up your array as I did in test 2 or tests 4 and 5.

Unfortunately the problem that I have with test 2's method is two fold. Primarily it requires that I enter data and retrieve it via coordinates, and secondarily it doesn't allow me to pull out parent arrays. While there are workarounds to both of these to some degree, it appears to be more of a hassle than it's worth. But I suppose I should write some benchmark tests to see how quickly each type of array stores and retrieves data and how memory efficient each array type is before I make an ultimate judgement call.

But to give you an idea of my issue with them, lets say that I want to store dialog for characters inside arrays and I have a function that parses them and reads the data back out. My problem becomes how do I pass that data to the function? We've established that I can't just say I need line 8 of dialog (dialog[8] ) to grab all of the data for that line. So do I then pass it like this? my_dialog_engine( dialog[8,1], dialog[8,2], ... ) and what happens if there becomes a time when I need more than 10 arguments?

The problems that I run into are that I ultimately am looking for a solution to enter unknown array length sets into arrays and retrieve them for passing blocks of data to functions as needed without having to pass the entire array structure every time (for performance and memory reasons). It could be that I'm just too inexperienced with GML to know proper ways of accomplishing this (and my background with PHP's very robust and reliable array tools are certainly making this learning curve harder for me than I'm sure it is for others). But sadly it appears that for now my best route moving forward (at least with my current project is test 5 - or a derivation of it).

@TheouAegis Interesting. I used Game Maker 3,4,5 & 6 and just came back at the start of the GMS 2's beta so it's been fun seeing all the new features (and the really nice new interface design). But come to think of it I never once needed to use an array for any of the old games I made so this is all uncharted territories for me. Glad to hear they are making progress though. So who knows? Maybe someday GMS 3 will come out and arrays will be no longer limited to 2d arrays and regardless of how you store your data all of the options to retrieve it will work and all these issues of mine will be distant memories! And maybe someday I'll even be able to use variable[0][3] to pull my data ;)
 
D

Drenathor

Guest
@IndianaBones That's encouraging! It will be particularly interesting to see if chained arrays and accessors would unify some of these inconsistency issues. Personally whether they want to store data as arrays or pointers doesn't matter to me, I just want consistency when retrieving data as opposed to, oh you can't get that piece of data because you stored it the "wrong" way. But thanks for the heads up! I'll be keeping an eye on the patch notes for this item in particular :)
 
D

Drenathor

Guest
@TheouAegis I discovered an interesting workaround to test 4. Apparently setting tmp = []; forces GMS to get new pointer locations for the array when repopulated. So now test 5 is no longer a necessary evil as the solution to test 4's problems seems to be only 9 extra characters:

Code:
// test 4
tmp[0] = 0;
tmp[1] = 1;
tmp[2] = 2;
tmp[3] = 3;
test4[0] = tmp;
tmp = []; // reset the array to prevent pointer issues
tmp[0] = 4;
tmp[1] = 5;
tmp[2] = 6;
tmp[3] = 7;
test4[1] = tmp;

show_debug_message( test4 ); // {{{{0,1,2,3},},{{4,5,6,7},}},}
 
@TheouAegis I discovered an interesting workaround to test 4. Apparently setting tmp = []; forces GMS to get new pointer locations for the array when repopulated.
I feel this is explained adequately in the documentation myself.

If you set an array to nothing, i.e., tmp = [] or as the documentation says, tmp = 0, it basically destroys the array.

Then, when you on the following line declare the array again tmp[0] = 4, you are creating a brand new array.

Not sure if the following is answered above, please ignore if it is:

On the subject of pointers, an array variable is not strictly a pointer, its an integer index to the array. (Or you could think of it as a "handle" as well I suppose)

The first array you declare in your code will get an index/handle of 0. The next array will get a handle of 1 etc....

So if you do this:

Code:
tmp[0] = 0

show_debug_message("array index : " + string(tmp))

tmp = 0 // destroy the array

tmp[1] = 0 // create a new array but using the same variable to hold a reference to it

show_debug_message("array index : " + string(tmp))
You will get the output:

array index : 0
array index : 1

This is why in Test2 above where you wrote:
//show_debug_message( test2[0] ); // 0 // Where is all the data?
...you got 0 as the output. Although you declared test2 as a 2d array, when you write test[0], this is returning the index to the first row of the 2d array which is a 1d array itself. It was printing the array handle (being 0 because its the first array you created in code)

And when you put:

tmp = test2[1]

That is going to get you the handle/index to the 2nd row of the 2d array, which itself is a 1d array, so when you tried to print "tmp", it return 1, because that is the handle to the array (being basically the 2nd array created)

Which is interesting because that means a 2d array is basically a 1d array which contains array indexes to other 1d arrays if my understanding is correct.

Which would explain why GameMaker can have 2d arrays with rows of varying lengths, as each row is a seperate array.

I just learned something myself (If my logic is correct)
 
Top