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
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!
— Vadim (@YellowAfterlife) December 20, 2016
In other words, do this:
//creation of stack: a = ds_stack_create(); //aliases to the stack b = a; //copy of stack id stored in a -- don't do it this way! c = a[@0]; //reference to stack id stored in a //destroy the data structure AND clear the variable ds_stack_destroy(a); 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 = ds_stack_create(); ds_exists(a, 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, //we re-used the same id that had been used by the stack referenced by a, //and copied to b by value. ds_exists(c, ds_type_stack); //returns false, because c references a rather than storing a copy of the id it had stored. ds_exists(d, ds_type_stack); //returns true, because we created ds_stack d.
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.