video games, programming, the internet, and stuff

GameMaker data structures: a cautionary tale

I learned recently that when you are working with data structures in GameMaker, you have to be very careful.

When you create a data structure in GMS, the ds_*_create() function returns an integer value which is the id of the data structure. You then use this id whenever working with that data structure.

When you destroy the data structure, this frees up the memory that the data structure used, and the data structure is now longer accessed.

If you take that id and test to see if the data structure exists, using the ds_exists() function, it should return false.

The operative word being “should”. It doesn’t always.

Why not? Because, the GameMaker runtime reuses the ids, and if you create a new data structure, it will hand out that id that belonged to the data structure that was just destroyed.

To illustrate the example, we’ll use a ds_stack:

var a = ds_stack_create();
var b = ds_stack_create();
show_message(string( ds_exists(a, ds_type_stack)));

You would think that ds_exists(a, ds_type_stack) would return false. But it doesn’t, because ds_stack b just moved into ds_stack a‘s old address.

Apparently the correct practice, which is not mentioned in the manual, is to immediately clear the value of a after destroying the ds_stack that it points to, like so:

a = undefined;

If you do that, then a no longer points to an address that can potentially be used by another data structure, and you’re safe, even if b has the same address as a once did.

Of course there’s still a problem if multiple variables all point to that same destroyed data structure. This is probably a somewhat rare circumstance, but could possibly by the case in some circumstances, for example if you have a global variable, or controller object, holding a data structure that is accessed by multiple instances of some common object. Keeping track of every variable that references the destroyed data structure and clearing all of them when the data structure is destroyed is not an easy thing to do.

Update: Thanks to @YellowAfterLife for the very clever tip!

In other words, do this:

//original declaration:

a[0] = ds_stack_create();
b = a[0];
c = a[0];

//destroy the data structure AND clear the variable
a = -1; // this destroys the array a[], and therefore kills b and c's references to it

d[0] = ds_stack_create();

ds_exists(a[0], ds_type_stack); //returns false
ds_exists(b, ds_type_stack); //returns false, despite not knowing to clear b
ds_exists(c, ds_type_stack); //returns false, despite not knowing to clear c
ds_exists(d[0], ds_type_stack); //returns true

Actually, don’t do that. I just tested it, and it doesn’t work. I must have misunderstood @YellowAfterlife or maybe he didn’t think through his advice carefully enough. I’m currently waiting for an answer on this, and will update it with something that does work, or a full retraction, once I have it.

It’s debatable whether the re-use of handles like this is in fact a good practice, but at the moment it’s the way GameMaker works, and has worked, for years. To some extent, re-use is probably impossible to avoid entirely, but the discovery that they are re-used so soon after being freed is a bit of a shocker.

More information.

Leave a Reply

csanyk.com © 2016
%d bloggers like this: