csanyk.com

video games, programming, the internet, and stuff

GameMaker data structures: a cautionary tale

When you work with data structures in GameMaker, you have to be very careful with references to a data structure that has been destroyed.

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 can no longer be accessed.

If you take that id that belonged to the destroyed data structure, 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 of data structures, 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();
ds_stack_destroy(a);
 
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:

ds_stack_destroy(a);
a = undefined;

If you do that, then a no longer points to an address that can potentially be assigned to new data structure, and so 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:

//creation of stack:
a[0] = ds_stack_create();
//aliases to the stack
b = a[0];  //copy of stack id stored in a[0] -- don't do it this way!
c = a[@0]; //reference to stack id stored in a[0]
 
//destroy the data structure AND clear the variable
ds_stack_destroy(a[0]);
a = undefined; // this destroys the array a[], and therefore kills all references to the ds_stack.
 
//create a new stack, let's see what happens with our aliases
d[0] = ds_stack_create();
 
ds_exists(a[0], ds_type_stack); //returns false, because the array was destroyed.
ds_exists(b, ds_type_stack);    //returns true, but misleadingly so, because when we created d[0], 
                                //we re-used the same id that had been used by the stack referenced by a[0], 
                                //and copied to b by value. 
ds_exists(c, ds_type_stack);    //returns false, because c references a[0] rather than storing a copy of the id it had stored.
ds_exists(d[0], ds_type_stack); //returns true, because we created ds_stack d[0].

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. But the discovery that they are re-used (and so soon after being destroyed) is a bit of a shocker.

More information.

Leave a Reply

csanyk.com © 2016
%d bloggers like this: