GMS 2.3+ Strange struct variable behavior

Tirennk

Member
I have seen an strange behavior when declaring variables inside structs. The following code returns the x value of the instance that creates the struct even though it should return an error. I discovered this tracing a bug for a much bigger struct that I'm using and I really don't understand it.

struct_bug.PNG
It also happens for any other type of variable.

Can someone explain if this is intended? If it is intended I think I could be missing something important about structures in GM 2.3 +

Thank you!!
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
You're creating an array that is 1 in length then checking the value at 1000... this will return 0 by default and that probably is the object_index for the instance you are getting the result for... so you're essentially checking object_index.x. Change the initialization of the array to 1001 and use undefined as the default value and you'll get an error I'm sure. Keep in mind that all asset indices in GM are INTEGER values, so this kind of error is possible if you're not aware of the potential for issues.
 

Tirennk

Member
That is actually it!

Although I didn't expect this to happen because if you try to create an array as you will do normally outside an structure and then call position 1000 it would throw an error as the index is obviously greater than array length.

Thanks for helping me find what I was missing!
 

chamaeleon

Member
It is very odd that it does not crash when it is inside a struct but does when it is not.
GML:
testStruct = function() constructor {
    static arr = array_create(1);
}

var test = new testStruct();
show_debug_message(object_get_name(0));
show_debug_message(test.arr[1000].x);
show_debug_message(test.arr[2000]);
show_debug_message(object_get_name(test.arr[2000]));

var foo = array_create(1);
show_debug_message(object_get_name(0));
show_debug_message(foo[1000].x);
show_debug_message(foo[2000]);
show_debug_message(object_get_name(foo[2000]));
Code:
obj_wall
96
0
obj_wall
obj_wall
ERROR!!! :: ############################################################################################
ERROR in
action number 1
of Create Event
for object obj_generic:


Push :: Execution Error - Variable Index [1000] out of range [1] - -7.<unknown variable>(100110,1000)
 at gml_Object_obj_generic_Create_0 (line 13) - show_debug_message(foo[1000].x);
############################################################################################
gml_Object_obj_generic_Create_0 (line 13)
All messages are displayed for the struct version, while it crashes on the first use of foo with a high index.
 

Tirennk

Member
I think it has to do with the way the line is evaluated. As pointed by Nocturne, the default value for an undefined element inside an array inside an struct is 0? Also I can see that what GM actually does is to evaluate first the variable arr[1000] and then evaluates the result as 0.x which is the object_index of my object (because have created only one object in this test "project").

You can see this happens also when you try to run the following code. It will show you 12 as it is evaluating 0.arr[0] instead of giving an error

GML:
testStruct = function() constructor {
   
    static a = 0
   
}

test = new testStruct()

arr[0] = 12

show_message(test.a.arr[0])
 

chamaeleon

Member
I think it has to do with the way the line is evaluated. As pointed by Nocturne, the default value for an undefined element inside an array inside an struct is 0? Also I can see that what GM actually does is to evaluate first the variable arr[1000] and then evaluates the result as 0.x which is the object_index of my object (because have created only one object in this test "project").

You can see this happens also when you try to run the following code. It will show you 12 as it is evaluating 0.arr[0] instead of giving an error

GML:
testStruct = function() constructor {
  
    static a = 0
  
}

test = new testStruct()

arr[0] = 12

show_message(test.a.arr[0])
Why does a struct care about what data type a member variable is? The concept "the default value of an undefined element inside an array inside a struct" smells bad to me. There shouldn't be any difference between that and "the default value of an undefined element inside an array".
 

Roldy

Member
Why does a struct care about what data type a member variable is? The concept "the default value of an undefined element inside an array inside a struct" smells bad to me. There shouldn't be any difference between that and "the default value of an undefined element inside an array".
Definitely a bug and has nothing to do with structs. The best I can tell is internally when evaluating dot operator on structs it simply goes down a different code path that doesn't respect array lengths; it probably has more to do with the abomination that is GML arrays than it does with structs.

It is interesting that when using an array accessor it errors correctly:

GML:
testStruct = function() constructor {


    static arr = array_create(1);

    static testVarA = self.arr[1000]; // THIS does not catch the out of bounds error
    static testVarB = arr[1000];        // this throws an error correctly (out of bounds)

    static testVar1 = self.arr[@ 1000];    // this throws an error correctly (out of bounds)
    static testVar2 = arr[@ 1000];        // this throws an error correctly (out of bounds)

}
EDIT: You can do the same using no struct, and just an object instance. Here is object create event:

GML:
a =  array_create(1);

b = id.a[1000]; // THIS does not catch the out of bounds error
c = self.a[1000]; // THIS does not catch the out of bounds error

d = a[1000]; // this throws an error correctly (out of bounds)
Just for giggles I tried to wrap it in a try/catch block but no exception is thrown either... makes sense.
 
Last edited:

Roldy

Member
FYI: this is a 2.3 bug. The following line will correctly throw errors in 2.2.5 but not 2.3
GML:
// Create Event
a =  array_create(1);

b = id.a[1000]; // No error in 2.3.   Does error in 2.2.5
c = self.a[1000]; // No error in 2.3.   Does error in 2.2.5
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Just checked the manual, and it has this to say:

"However, if you try to access a value in an empty array then you will get an error. In fact, you should always take care to only access valid array positions, as trying to access a value outside of an array will also give an error."

So, yeah, if it's not giving an error under some circumstances it is a bug. I forgot that they'd changed this behaviour some time ago...
 
@Nocturne I also noted something...

GML:
var a = 1;
a[10] = 0; // this doesn't error (just converts 'a' into an array)
I don't recall how it used to be though, but I can see this leading to a lot of hard to debug errors :/
 

kburkhart84

Firehammer Games
@Nocturne I also noted something...

GML:
var a = 1;
a[10] = 0; // this doesn't error (just converts 'a' into an array)
I don't recall how it used to be though, but I can see this leading to a lot of hard to debug errors :/
This actually seems normal to me, due to the dynamic typing that GML variables use. In other languages it would error since a variable is always the original type, but not in GML. Now, I bet if you used a[10]+=1 that it would error since that actually accesses the value that isn't there.
 
This actually seems normal to me, due to the dynamic typing that GML variables use.
I can see your point but what I call dynamic typing is:
GML:
a = 1;
a = [];
a = undefined;
a = "hello";
you can change them when you assign to them.

On the other hand:
GML:
a = 1;
a[10] = 10 // this feels strange... don't know..
did it always work like that?! :) I'm drawing a blank right now đŸ˜†
 

kburkhart84

Firehammer Games
I can see your point but what I call dynamic typing is:
GML:
a = 1;
a = [];
a = undefined;
a = "hello";
you can change them when you assign to them.

On the other hand:
GML:
a = 1;
a[10] = 10 // this feels strange... don't know..
did it always work like that?! :) I'm drawing a blank right now đŸ˜†
a = []; is basically the same thing as i[10] = 0 in the sense that it is changing the type. It may not be quite as explicit, but if you are setting an array index, it is basically assumed that you intend for that variable to now be an array. And yes, as far as I know, it has always worked this way.

That said, I came from static-type languages, and I almost never change the type of a variable. The only time I take advantage of dynamic typing is in the case of function arguments where the function does something different based on the type of variable the argument is. An example is the famous group of strings and numbers for use of converting all to a string(like for debug logging). Another example I have in my input system is that it supports callbacks after something completes, and those callbacks can be either global functions OR instance methods, and one can be executed simply by adding parenthesis, while the other needs script_execute() to run when you pass function references. Note that both of those examples aren't even exactly changing variable types after the fact(since the variables are local to the function and only used at that moment), but it serves as the only times I ever take advantage(or accamodate) dynamic typing in GML.
 

chamaeleon

Member
My preference would have been to only allow assignment to an array index if the variable holds an array already. If it doesn't assigning an array to the variable first, empty or not, should be required to avoid an error. Once it holds an array it can then proceed to increase the size of the array as one assigns to indices within it. I realize this may break existing code left and right and won't argue for the change, I just wish it was that way from the beginning.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
I don't recall how it used to be though, but I can see this leading to a lot of hard to debug errors :/
This is definitely normal and has always been this way... Basically you are reassigning the variable "a" to be an array and initialising it with 11 values... IIRC, a[0] - a[9] will all equal 0 in this case. You can reassign any variable at any time to hold any value pretty much. It's not good practice to do so, but GM lets you get away with it and always has.
 

Tirennk

Member
Gamemaker has always let you set arrays using arr[10] = 1 or whatever thing you want. There are setters and getters also, there is a mention to this in alarm_set where the manual says " This function can be used to set an alarm. You supply the alarm number from 0 to 11, and then the value to set the alarm to. The value must be an integer value, and you can set it to -1 to stop the alarm. This is an alternative method to setting the alarm array directly. "

On the other hand, it seems that the main topic of this thread is related to a new bug which causes an array to return 0 when calling an nonexistent element from an array when it is used as method of id or self.

I will report it as bug. Hope they fix it.


Thank you all for your time and help!
 
Top