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
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(); ds_stack_destroy(a); var b = ds_stack_create(); show_message(string( ds_exists(a, ds_type_stack)));
You would think that
(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 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!
— Vadim (@YellowAfterlife) December 20, 2016
In other words, do this:
//original declaration: a = ds_stack_create(); //aliases b = a; c = a; //destroy the data structure AND clear the variable ds_stack_destroy(a); a = -1; // this destroys the array a, and therefore kills b and c's references to it d = ds_stack_create(); ds_exists(a, 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, 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.