GML [TEXT TUTORIAL] Array Accessor Explained (@)

GM Version: Studio 2
Target Platform: Windows / All
Download: n/a
Links: n/a

[PROBLEM]
When you create a reference of an array and edit it your original array stays the same and only the "copy" gets modified.


[EXPLANATION]
There are four different array behaviour you need to understand.

(1) Editing an array directly will edit the original array and consequently ALL the references.
GML:
var array1 = [1, 2, 3, 4, 5];
var array2 = array1;
array1[0] = "Hello";
show_debug_message(array1) // ["Hello", 2, 3, 4, 5];
show_debug_message(array2) // ["Hello", 2, 3, 4, 5];
(2) Assigning an array to a new variable will just share the pointer to that array
GML:
var array1 = [1, 2, 3, 4, 5];
var array2 = array1;
show_debug_message(array1) // [1, 2, 3, 4, 5]; <- same pointer
show_debug_message(array2) // [1, 2, 3, 4, 5]; <- same pointer
(3) Editing a reference to an array will only edit the reference and not the original
GML:
var array1 = [1, 2, 3, 4, 5];
var array2 = array1;
array2[0] = "Hello";
show_debug_message(array1) // [1, 2, 3, 4, 5];
show_debug_message(array2) // ["Hello", 2, 3, 4, 5];
(4) This is similar to the third one, passing an array as an argument counts as making a reference to it.
GML:
function myFunc(array) {
    array[0] = "Hello";
}

var array1 = [1, 2, 3, 4, 5];
myFunc(array1);
show_debug_message(array1) // [1, 2, 3, 4, 5];
This last one happens because the "array" variable of the function is actually a reference to the array1, so in the backstage GMS does array = array1 and then passes the argument to the function. Every time you set a variable to an array that variable is given the pointer to the array, BUT as soon as you edit it.. the array gets copied and dereferenced from the original.
It was like this all the way back from GMS1.4 if I'm not mistaken, so this is NOT new to 2.3.


[ACCESSORS]
You probably heard of accessors before specially if you used ds_lists, ds_maps, ds_grids. Those are symbols that allow for quick edit of entries in the respective data structures
GML:
myList[| 0] = 12; // the accessor is -> |
myMap[? "name"] = "value"; // the accessor is -> ?
myGrid[# 1, 2] = true // the accessor is -> #

[SOLUTION]
What many people don't know is there is a special accessor for arrays too, that apart from allowing quick access to values stops GMS from copying the original array and instead makes an in-place edit.
The array accessor is "@" (at symbol)
So whenever you want to edit the original array instead of copying it you just need to use:
GML:
var array1 = [1, 2, 3, 4, 5];
var array2 = array1;
array2[@ 0] = "Hello";
show_debug_message(array1) // ["Hello", 2, 3, 4, 5]; <- same pointer
show_debug_message(array2) // ["Hello", 2, 3, 4, 5]; <- same pointer

[NOTE]
The array accessor allows for an edit-in-place so as you might have guessed you don't need to use it when reading from an array;
GML:
var array1 = [1, 2, 3, 4, 5];
var value = array1[@ 0] // this is not needed and as far as I know carries some overhead.

Here xD from xDGameStudios,
Good coding to you all.
 
Last edited:

chamaeleon

Member
The difference in behavior between (1) and (3) is an abomination in my opinion. I firmly believe the correct behavior should be, given references and copy-on-write, that (1) should result in array1 having a new array with the modified value while array2 has a reference to the original array. What variable I use to change the value shouldn't dictate whether copy-on-write takes place or not.

Also, this behavior is not consistent with using array_create or a function returning an array value, both of which exhibit the behavior I'd prefer.
GML:
var array1 = array_create(5, 0);
var array2 = array1;
array1[0] = "Hello";
show_debug_message(array1);
show_debug_message(array2);
Code:
[ "Hello",0,0,0,0 ]
[ 0,0,0,0,0 ]
GML:
function get_array() {
    return [1, 2, 3, 4, 5];
}
var array1 = get_array();
var array2 = array1;
array1[0] = "Hello";
show_debug_message(array1);
show_debug_message(array2);
Code:
[ "Hello",2,3,4,5 ]
[ 1,2,3,4,5 ]
In short, there should not be any "edit of the original array", unless I explicitly request it using the @-accessor, or the variable I use happen to be the only reference to the array in which case copy-on-write can be skipped or optimized away.
 
Last edited:
The difference in behavior between (1) and (3) is an abomination in my opinion.
I do understand what you mean (and it makes sense) but I'm just documenting how it works :)

the difference from (1) to (3) and also the reason for the different behaviour when dealing with functions that return arrays "MIGHT" be:

GML:
a = [2, 3, 4, 4]; // "a" is interpreted as the actual array literal.

a = array_create(3, 1) // "a" is an indirect reference to the literal.
this would be my guess!
 
Last edited:

chamaeleon

Member
I do understand what you mean (and it makes sense) but I'm just documenting how it works :)
Oh, certainly! Not pointing a single accusing finger in your direction. :) In fact, I'm thankful for the example, as it did not fit my mental model of how would work, and it might trip me up in the future. I have hopefully been lucky in avoiding the issue (as far as I know... Knock on wood, throw salt over my shoulder, etc.), and knowing this oddity in behavior might keep it that way.

Another experiment
GML:
function array1() {
    var a = [];
    for (var i = 4; i >= 0; i--) {
        a[i] = i+1;
    }
    return a;
}

function array2() {
    var a = [1, 2, 3, 4, 5];
    return a;
}
GML:
var a = array1();
var b = a;
a[0] = "Hello";
b[1] = "World";
show_debug_message("a = " + string(a));
show_debug_message("b = " + string(b));

var c = array2();
var d = c;
c[0] = "Hello";
d[1] = "World";
show_debug_message("c = " + string(c));
show_debug_message("d = " + string(d));
Code:
a = [ "Hello",2,3,4,5 ]
b = [ "Hello","World",3,4,5 ]
c = [ "Hello",2,3,4,5 ]
d = [ 1,"World",3,4,5 ]
In my view, a complete inconsistency between changing the first element of a versus c.
 

chamaeleon

Member
One more example just for fun. Maybe I should file a bug report at some point...
GML:
function array1() {
    var a = [];
    for (var i = 4; i >= 0; i--) {
        a[i] = i+1;
    }
    return a;
}
GML:
var a = array1();
var b = a;
a[0] = "Hello";
b[1] = "World";
show_debug_message("a = " + string(a));
show_debug_message("b = " + string(b));

var c = array1();
var d = c;
c[0] = "Hello";
d[1] = "World";
show_debug_message("c = " + string(c));
show_debug_message("d = " + string(d));
Code:
a = [ "Hello",2,3,4,5 ]
b = [ "Hello","World",3,4,5 ]
c = [ "Hello",2,3,4,5 ]
d = [ 1,"World",3,4,5 ]
 
Top