Nested Data Structures

ome6a1717

Member
I'm having issues figuring out my nested data structures. I keep receiving errors. I apologize for the wall of text here...

Long story short, what this is trying to do:
I have tons of o_stageBlocks (obj) that I place over parts of a level to piece them together randomly. I then use the with command to retrieve any objects overlapping them, get all the tile data, and put them to the appropriate list/grids in the constructors. Then upon room start, I create a random list of the rooms, and in order build the stage from these bits. The tiles load perfectly, and honestly when I was NOT using a map and instead used a struct, everything was fine. I then realized that a map would give me more flexibility (hence the switchover)

ROOM START EVENT
GML:
with(o_stageBlock)
{

// this is a constructor that creates a couple grids and a list for objects (worldDataBlob.objBlob is a ds_list initiated in the constructor)
var worldDataBlob = new createWorldBlob([0,0],[0,0],[0,0]);
           
    #region OBJECT DATA
        var objs = ds_list_create();
        var num = instance_place_list(x,y,all,objs,false);
        
        var ignoreObject = [o_stageBlockLeft, o_stageBlockRight];
        
        if (num > 0)
        {
            // loop through all objects
            for (var i=0;i<num;i++)
            {
                var item = objs[| i];   
                // makes sure to not include specific objects in array (function that returns true or false if object is found in array)
                if (!in_array(item.object_index,ignoreObject))
                {
                    var map = ds_map_create();
                    
                    ds_map_add(map,"x",abs(startBlock.x - item.x));
                    ds_map_add(map,"y",item.y - startBlock.y);
                    ds_map_add(map,"xscale",item.image_xscale);
                    ds_map_add(map,"yscale",item.image_yscale);
                    ds_map_add(map,"index",item.object_index);
                    ds_map_add(map,"angle",item.image_angle);
                    ds_map_add(map,"layer",layer_get_name(item.layer));
                
                    // OBJ SPECIFIC ONCREATE VARIABLES
                    switch(item.object_index)
                    {
                        case o_portal: ds_map_add(map,"type",item.type); break;
                    }
                    ds_list_add(worldDataBlob.objBlob, map);
                    //show_message(worldDataBlob.objBlob[| index][? "x"]);
                    //ds_list_mark_as_map(worldDataBlob.objBlob,i);
                    ds_map_destroy(map);                   
                }
            }
            // adds this list of maps to the objBlob list
            //ds_list_add(worldDataBlob.objBlob,map);
        }
        
        ds_list_destroy(objs);
        
    #endregion

}
This part of the code works. Each "worldDataBlob" is saved to a ds_list on this particular object. I can check the references of worldDataBlob.objBlob[| int][? KEY] and I get the correct result for all objects. I then add the worldDataBlob struct to a list on the object called orderedList.

This is where I start to get errors:

ROOM START (I'm on purpose hiding needless room check code)

GML:
// this list is created in a very long function that I'm not going to put here - I've confirmed it works
 var amt = ds_list_size(orderedList);
    
    // loop through each selected blob choice and create the final map
    for (var a=0;a<amt;a++)
    {
// current room chosen
        var curIndex = orderedList[| a];

        #region CREATE OBJECTS
            for (var i=0;i<ds_list_size(curIndex.objBlob);i++)
            {
                var item = curIndex.objBlob[| i];
                
                var _obj = item[? "index"];
                var _x = item[? "x"];
                var _y = item[? "y"];
                var _layer = item[? "layer"];
                
                var obj = instance_create_layer(_x,_y,_layer,_obj);
                obj.image_xscale = item[? "xscale"];
                obj.image_yscale = item[? "yscale"];
                obj.image_angle = item[? "angle"];
            }   
            
        #endregion
Now, this SHOULD loop through each object in the constructor's list that was saved, retrieve the map and use all the correct variables. For some reason, looking at the debugger, I see this:

orderedList (list of structs (worldDataBlobs)) > structs (list, 2 grids, and a variable or two) > list of entries (all have value 22 in debugger), within the list, if I try to view as ds_map (which is what it should be) it says size =-1 invalid structure. However, if I view as a list, EVERY entry has a ds_list_size of 2, the first position(0) being empty, and the 2nd (1) being a map (however the values look incredibly strange - the key is 32098 or some number like that and the value is seemingly 0-22). Somehow this tells me something is wrong in the creation code, but I can't find what it is.

I'm sure it's as simple as a line being in the wrong bracket, but I cannot, for the life of me, figure it out. The actual code for this is much larger than this, I'm just trying to cut out the extra code to needlessly look at.



Any ideas?
 

Simon Gust

Member
I see that for every map you create and fill with data, you instantly destroy it afterwards. You save the map index in a list but it does not help if the map with that index does not exist. Your index points to nothing.
 

ome6a1717

Member
UPDATE - Possibly solved it...ds_map_destroy(map) is deleting the map from the worldObjectBlob...why would this be happening?
 

ome6a1717

Member
I see that for every map you create and fill with data, you instantly destroy it afterwards. You save the map index in a list but it does not help if the map with that index does not exist. Your index points to nothing.
You beat me to it! How would I then add the map to the list and then destroy the local map so I don't get a memory leak?
 

Simon Gust

Member
You beat me to it! How would I then add the map to the list and then destroy the local map so I don't get a memory leak?
you don't get a memory leak. Creating a map and filling it takes memory, you can't just have free memory.
This code is in a room start event, so every room you create maps, to me that would indicate you should destroy the maps in the room end event as the data inside would no longer be needed or be not up to date.
If not, create event might be better.
 

ome6a1717

Member
you don't get a memory leak. Creating a map and filling it takes memory, you can't just have free memory.
This code is in a room start event, so every room you create maps, to me that would indicate you should destroy the maps in the room end event as the data inside would no longer be needed or be not up to date.
If not, create event might be better.
Ah so I should instead have it as a variable on the object instead of a local var? I delete the object right after the room is put together, so I need to destroy it somehow.
 

Simon Gust

Member
Ah so I should instead have it as a variable on the object instead of a local var? I delete the object right after the room is put together, so I need to destroy it somehow.
That can work, but then why would you need a map in the first place? Just have normal instance variables that point to the same data.
 

Simon Gust

Member
Actually, I take back what I said, having the map as instance variable has no purpose since you need multiple maps per o_stageBlock.
You could have the list that holds the maps be an instance variable for o_stageBlock however. And then access the list like this
GML:
with(o_stageBlock)
{
    var size = ds_list_size(objs);
    for (var n = 0; n < size; n++) {

    } 
}
Sorry, it's kind of hard for me to decipher what your code is actually supposed to do.

You just have to remember to destroy everything when you change rooms.
 

ome6a1717

Member
Actually, I take back what I said, having the map as instance variable has no purpose since you need multiple maps per o_stageBlock.
You could have the list that holds the maps be an instance variable for o_stageBlock however. And then access the list like this
GML:
with(o_stageBlock)
{
    var size = ds_list_size(objs);
    for (var n = 0; n < size; n++) {

    }
}
Sorry, it's kind of hard for me to decipher what your code is actually supposed to do.

You just have to remember to destroy everything when you change rooms.
No worries - that's kind of what I'm doing in a sense. So does that mean if I add var size list to a struct, as long as I delete the list in the STRUCT, the var size list is also destroyed?
 

Simon Gust

Member
No worries - that's kind of what I'm doing in a sense. So does that mean if I add var size list to a struct, as long as I delete the list in the STRUCT, the var size list is also destroyed?
any variables with var are already gone with the passing of a closing bracket }
Datastructures inside those variables are not however. These have to be destroyed manually, and hopefully the index pointing to them in memory isn't lost.

In your original code, you were creating maps as var, this was totally ok because you were transfering thier indexes into a list which you had full control over.
 

ome6a1717

Member
any variables with var are already gone with the passing of a closing bracket }
Datastructures inside those variables are not however. These have to be destroyed manually, and hopefully the index pointing to them in memory isn't lost.

In your original code, you were creating maps as var, this was totally ok because you were transfering thier indexes into a list which you had full control over.
Got it! Okay, phew. Thank you.
 

Nidoking

Member
Remember, a memory leak is when you have memory allocated and no longer have a reference to it anywhere. If you do var l = ds_list_create(), the list has been allocated, but you have a local variable l referencing it, so it can still be accessed and destroyed. If you then do something like inst.MyList = l, the instance is holding a reference to the list, and as long as that reference exists, you can still use the list. It's when you overwrite the MyList variable or destroy the instance that the reference would be lost, and the memory would be leaked, because you no longer have something you can pass into ds_list_destroy to release it. This is why I recommend either creating data structures as local variables and destroying them before leaving the scope, or creating them in a Create event and destroying them in the Clean Up event. Obviously, that can't always be done, but if you treat create and destroy functions the way you treat brackets, always having to be paired, you'll have significantly fewer memory leak issues.
 

ome6a1717

Member
Remember, a memory leak is when you have memory allocated and no longer have a reference to it anywhere. If you do var l = ds_list_create(), the list has been allocated, but you have a local variable l referencing it, so it can still be accessed and destroyed. If you then do something like inst.MyList = l, the instance is holding a reference to the list, and as long as that reference exists, you can still use the list. It's when you overwrite the MyList variable or destroy the instance that the reference would be lost, and the memory would be leaked, because you no longer have something you can pass into ds_list_destroy to release it. This is why I recommend either creating data structures as local variables and destroying them before leaving the scope, or creating them in a Create event and destroying them in the Clean Up event. Obviously, that can't always be done, but if you treat create and destroy functions the way you treat brackets, always having to be paired, you'll have significantly fewer memory leak issues.
Right - so since I'm saving the reference of the map to the list, once I use the maps in the constructor, deleting them will clear the memory of those maps (even though I created the map in a local var). I get it, finally!
 

chamaeleon

Member
Right - so since I'm saving the reference of the map to the list, once I use the maps in the constructor, deleting them will clear the memory of those maps (even though I created the map in a local var). I get it, finally!
You did not "create a map in a local var". You just stored a reference to a map. A data structure reference or id is just a number (0 and up). How you store it means very little. If you know a data structure's reference because you have it in a local variable (while it exists in the currently executing function or event), or in another data structure, be it a list or an array, or as an instance or global variable, you can at some point say you want to free the map (for the sake of argument, it applies to all the other ds data structures), given the available storage of its value.

To reinforce the concept of a reference. The reference you store in a variable or data structure has no idea it is a reference. It is just a regular number. The variable or data structure you store the reference in does not know it is a reference to a data structure either. It is just a number like any other number.

GML:
var foo = ds_map_create(); // assume this is the first map, resulting in an id of 0
var bar = 0; // maybe bar is supposed to represent a sprite image_index to use elsewhere
ds_map_destroy(bar); // will happily destroy the map with id 0
GML:
var foo = ds_map_create(); // assume this is the first map, resulting in an id of 0
var bar = ds_list_create(); // assume this is the first list, resulting in an id of 0
ds_map_destroy(bar); // will happily destroy the map with id 0, while the list with id 0 will continue to exist
Did one mean to destroy the map referenced by foo, but used the wrong variable (bar), or did one mean to destroy the list referenced by bar, but called the wrong function (ds_map_destroy() instead of ds_list_destroy())? GMS will not prevent you from doing these things.

These mistakes may be unlikely, but it is an illustration of how the meaning of the value stored is non-existent without the context of which data structure function is being called.
 

ome6a1717

Member
You did not "create a map in a local var". You just stored a reference to a map. A data structure reference or id is just a number (0 and up). How you store it means very little. If you know a data structure's reference because you have it in a local variable (while it exists in the currently executing function or event), or in another data structure, be it a list or an array, or as an instance or global variable, you can at some point say you want to free the map (for the sake of argument, it applies to all the other ds data structures), given the available storage of its value.

To reinforce the concept of a reference. The reference you store in a variable or data structure has no idea it is a reference. It is just a regular number. The variable or data structure you store the reference in does not know it is a reference to a data structure either. It is just a number like any other number.

GML:
var foo = ds_map_create(); // assume this is the first map, resulting in an id of 0
var bar = 0; // maybe bar is supposed to represent a sprite image_index to use elsewhere
ds_map_destroy(bar); // will happily destroy the map with id 0
GML:
var foo = ds_map_create(); // assume this is the first map, resulting in an id of 0
var bar = ds_list_create(); // assume this is the first list, resulting in an id of 0
ds_map_destroy(bar); // will happily destroy the map with id 0, while the list with id 0 will continue to exist
Did one mean to destroy the map referenced by foo, but used the wrong variable (bar), or did one mean to destroy the list referenced by bar, but called the wrong function (ds_map_destroy() instead of ds_list_destroy())? GMS will not prevent you from doing these things.

These mistakes may be unlikely, but it is an illustration of how the meaning of the value stored is non-existent without the context of which data structure function is being called.
This should be in the manual...it's a much better way of explaining how these work.
 
These mistakes may be unlikely
Never underestimate the power of stupid mistakes! I've done the exact scenario you're describing (think you're destroying one type of DS while in actuality you're destroying another type) and it was such a massive pain to hunt down, lol...
 

Nidoking

Member
Another strong argument for including the name of the type of data structure in the variable name and dropping this habit of naming data structure variables with the names of different data structures. If you can write ds_list_destroy(obvious_map_variable); and not find something very wrong with that, something very much needs to change.
 
Top