• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

[SOLVED] Where's the leak coming from?

T

Taddio

Guest
I have this world generator that takes a picture and extracts data from colors and sets values in a grid according to the color. That works great, I can then set tiles according to the grid data.
The map is divided in a GRID, in which each cell stores a MAP for specific cell information.
It all works good, but I found I have a memory leak, and I can't find it. My CleanUp and destroy event are failproof (it seems) and running.
I provided the code below.
Can it be that somehow, destroying the top level grid does not free the containing maps automatically? According to the manual, it should, of I remember correctly.

This is the Create event.
Code:
/// @description Generate world
randomize();

#region Surface and save it to a sprite

 application_surface_enable(false);                        //We don't really need it

surf_terrain = 0;                                        //Will be the terrain surface
spr_surf = 0;                                            //Will be a sprite of the surface, to save it

//Create terrain surface
if(!surface_exists(surf_terrain)) {
    surf_terrain = surface_create(room_width, room_height);    //This is the terrain surface
    } else { surface_free(surf_terrain); }
spr_surf = 0;                                            //Will be a sprite of the surface, to save it

//Get scaling variables
var _spr_w, _spr_h, _x_scale, _y_scale;
_spr_w = sprite_get_width(spr_pixel_map);            //Get width of terrain sprite
_spr_h = sprite_get_height(spr_pixel_map);            //Get height of terrain sprite
_x_scale = (room_width/_spr_w);                        //get aspet ration in X
_y_scale = (room_height/_spr_h);                    //Get aspect ratio in Y

//Chooses a random sprite
var _spr_map, _map_num;
_map_num = sprite_get_number(spr_pixel_map)-1;
_spr_map = irandom(_map_num);

//Draw the sprites onto surface
surface_set_target(surf_terrain);
draw_sprite_ext(spr_pixel_map, _spr_map, 0, 0, _x_scale, _y_scale, 0, c_white, 1);
surface_reset_target();

//Copy the surface to a sprite
if(!sprite_exists(spr_surf)) {
    spr_surf = sprite_create_from_surface(surf_terrain, 0, 0, room_width, room_height, 0, 0, 0, 0);
}
 

#endregion

#region Creates world based on color of bg

var max_chunk_w = room_width/TILE_SIZE;                        //Get number of horizontalgrid cell in room
var max_chunk_h = room_height/TILE_SIZE;                    //Get number of vertical grid cell in room

var _buff = buffer_getpixel_begin(surf_terrain);            //Creates buffer and get surface data

grid_terrain = ds_grid_create(max_chunk_w, max_chunk_h);    //This ds_grid holds all terrain values

//Loop variables
var _i, _j, _col, _red, _blue, _green, _xx, _yy, _offset;
var _map, _type, _mine, _pass;
_offset = (TILE_SIZE/2);

//Loops through grid to find color of pixels at middle center of grid
for(_i=0; _i<=max_chunk_w-1; _i++) {                        //Loops through all ds_grid in X
    for(_j=0; _j <=max_chunk_h-1; _j++) {                    //Loops through all ds_grid in Y
        _xx = _i<<5;            //Multiply _i by 16 (TILE SIZE)
        _yy = _j<<5;            //Multiply _j by 16 (TILE SIZE)    
     
        //Create maps that will hold all grid cell data
        _map = ds_map_create();
     
        //Get pixel color of the middle-center of the grid cell
        _col = buffer_getpixel(_buff, _xx+_offset, _yy+_offset, room_width, room_height);    
     
        //Get the color value (colors swapped because RGB vs. BGR)
        _blue = (color_get_red(_col));
        _red = (color_get_blue(_col));
        _green = (color_get_green(_col));
     
        //Make cell value according to color
        if(_blue == 255){        //Light blue
            _type = TILE_WATER;
            _mine = 0;
            _pass = 0;
        } else
        if(_blue == 128){        //Dark blue
            _type = TILE_WALL;
            _mine = 0;
            _pass = 0;
        } else
        if(_red == 255){        //Light red
            _type = TILE_MG42;
            _mine = 0;
            _pass = 0;
        } else
        if(_red == 128){        //Dark red
            _type = TILE_SAND;
            _mine = 1;
            _pass = 1;
        } else
        if(_green == 255){        //Light green
            _type = TILE_GRASS;
            _mine = 1;
            _pass = 1;
        } else
        if(_green == 128){        //Dark green
            _type = TILE_CONCRETE;
            _mine = 0;
            _pass = 1;
        }
     
     
        ds_map_add(_map, "TYPE", _type);
        ds_map_add(_map, "MINE", _mine);
        ds_map_add(_map, "PASS", _pass);
     
     
        //Set grid value in relation to color
        ds_grid_set(grid_terrain, _i, _j, _map);
    }
}

//Copy grid to data object
with(obj_data){
    grid_terrain = ds_grid_create(max_chunk_w, max_chunk_h);    //This ds_grid holds all terrain values
    ds_grid_copy(grid_terrain, other.grid_terrain);
}

//Delete buffer because we don't need it anymore
buffer_delete(_buff);

#endregion


//Set tiles according to grid cell value
var max_chunk_w = room_width/TILE_SIZE;                            //Get number of horizontal grid cell in room
var max_chunk_h = room_height/TILE_SIZE;                        //Get number of vertical grid cell in room
var _ind;
var lay_id = layer_get_id("Tiles_terrain");
var map_id = layer_tilemap_get_id(lay_id);

for(var _w=0; _w <= (max_chunk_w-1); _w++) {
    for(var _h=0; _h <= (max_chunk_h-1); _h++) {
        var _cell = ds_grid_get(grid_terrain, _w, _h);
        var _type = ds_map_find_value(_cell, "TYPE")

        switch(_type){
            case TILE_WALL:
                _ind = 5;
                break
            case TILE_CONCRETE:
                _ind = 3;
                break;
            case TILE_GRASS:
                _ind = 1;
                break;
            case TILE_WATER:
                _ind = 4;
                break;
            case TILE_SAND:
                _ind = 2;
                break;
            case TILE_MG42:
                _ind = 6;
                break;
        }
        //DRAW TILES
        var _xx = _w<<5;                                //Get x position of tile
        var _yy = _h<<5;                                //Get y position of tile

        tilemap_set(map_id, _ind, _w, _h);
    }
}


//Destroy the world generator
instance_destroy();

And this is the Destroy and CleanUp event (they do run, I tried putting a breakpoint and they ran good)
Code:
/// @description DS, sprite and surface

if(ds_exists(grid_terrain, ds_type_grid)){
    ds_grid_destroy(grid_terrain);
}

if(surface_exists(surf_terrain)){
    surface_free(surf_terrain);
}

if(sprite_exists(spr_surf)){
    sprite_delete(spr_surf);
}

It does leak no matter if I copy the grid to obj_data and destroy the generator or if I don't destroy it and simply reset a room where he's the only object, so obj_data is to rule out of the suspects. There's only his one generator object in the room.
 
Can it be that somehow, destroying the top level grid does not free the containing maps automatically? According to the manual, it should, of I remember correctly.
You're thinking of ds_lists, where you can mark an entry as a map using ds_list_mark_as_map(), then destroying the list will clean up the maps.

ds_grid has no such function, you'll need to destroy them manually if you are not using them any more.
 

Joe Ellis

Member
If it would be alot of trouble manually deleting them, I recommend using arrays, you can put an array in each grid cell and they do get automatically deleted if nothing is using them

And how your using a map, with words, you can do that with an enum, enums are the way to do arrays that end up the same as a map, at least in the sense that you use words for each part

And they're alot faster cus they're dealing with integer constants rather than strings
 
T

Taddio

Guest
You're thinking of ds_lists, where you can mark an entry as a map using ds_list_mark_as_map(), then destroying the list will clean up the maps.

ds_grid has no such function, you'll need to destroy them manually if you are not using them any more.
Yep, this was it! Not that it's hard to implement at all, but that would be convenient if that was built-in. Not all top-level structures are lists...

@Joe Ellis yeah, I'm going to swap maps, it was just faster for debugging and getting it all to work asap. I don't really care about the speed while the map generates, but I plan to build my collisions around (ds) grid coordinates, plus it's 8-players simultanous, so it'll need to be quite efficient.
It works really good so far, i can make complete tiled maps from a 6 colors sprite made in sprite editor/MS Paint, it's kind of cool.
Im doing kind of a Couch-play Wolfenstein: ET demake in top-down view, and I want to be able to make (as in design) new maps really fast. Maybe even make a map editor for it using the same technique.

It could be

I think those also need to be destroyed to prevent leaks.
Yeah, I know that, but I thought destroying the grid holding the maps would also destroy the map, but see @IndianaBones post above, he explains my confusion wonderfully!
In a word, you were right, bro!
 
Last edited by a moderator:

Joe Ellis

Member
Sounds cool, if you want it to be fast as possible use arrays, a 2d array is a grid, I feel like a broken record, I'm always telling people to use arrays instead of ds's, but they're so much faster, and when you start doing something big like that the speed really matters
 
T

Taddio

Guest
Sounds cool, if you want it to be fast as possible use arrays, a 2d array is a grid, I feel like a broken record, I'm always telling people to use arrays instead of ds's, but they're so much faster, and when you start doing something big like that the speed really matters
I agree, on what you say, but I'm kind of a built-in functions kind of guy, and arrays have limited uses in this field, so ds are cool to sort, shuffle and stuff. in my case, arrays will be perfect, tho. Just to hold data, they are killer for sure!
 
I

immortalx

Guest
The things like sorting and shuffle, I can send you scripts that do that with arrays if you want
Joe forgive me if I'm wrong, but wouldn't a GML implementation of array sorting/suffle, be always slower than the build-in sorting/suffle of, say, ds_lists?
I too have a preference for arrays mainly because they're garbage-collected, but I feel like they created ds_lists to be as arrays on steroids, having some commonly used functions that one would normally had to write himself, which (I assume) are always going to be faster than hand-made ones.
 

Joe Ellis

Member
Joe forgive me if I'm wrong, but wouldn't a GML implementation of array sorting/suffle, be always slower than the build-in sorting/suffle of, say, ds_lists?
I too have a preference for arrays mainly because they're garbage-collected, but I feel like they created ds_lists to be as arrays on steroids, having some commonly used functions that one would normally had to write himself, which (I assume) are always going to be faster than hand-made ones.
Maybe those actual sorting functions themselves are faster, but the general usage of arrays, 90% of the time is reading and writing to them, it makes up for this, I haven't actually tested the ds sorting etc functions compared to my own ones, but the fact that ds's didn't cut it for vertices and faces, building vertex buffers, arrays were about 5 times faster, occasional sorting doesn't matter that much, its things that are done every step that matter, reading mostly and sometimes writing, which is in fairness about the same speed as ds_lists(writing), but reading is the most used and so the most important, arrays win
 

Joe Ellis

Member
That's really nice to hear, thanks for the explanation!
No problemo, I'm pretty anal with speed, I always test and find out the fastest way, its mainly cus in my program you deal with 100s & thousands of vertices so it makes a huge difference in that situation, but in most 2d games, you can do whatever you find most comfortable, there will probably be alot of time left, but then again, people always post things like pathfinding or things dealing with large amounts of objects, and in that case I always recommend using arrays, they're the best you can get, (so far) I can't wait till I get into the new gml stuff, that is surely gonna transform the way I and many other people are gonna do things
 
One semi-hidden problem with arrays is that if you are saving them in JSON format they will be converted to lists, so you'll have to go through the trouble of converting them back into arrays after you've loaded the data from the JSON.
 

Joe Ellis

Member
One semi-hidden problem with arrays is that if you are saving them in JSON format they will be converted to lists, so you'll have to go through the trouble of converting them back into arrays after you've loaded the data from the JSON.
Saving as json in itself is an inefficiency cus it has to convert everything to that, I always save arrays with buffers and use the minimum byte amount, but I know I am completely anal. But I realllllly don't like how people save to "jason" format, it really annoys me, cus they only do it cus there's a built in function, they have no real idea about saving data, and I'm annoyed cus they could learn how to save the data, with the only real way using buffers, you have to do it one step at a time, or make a script that automates it, which is how any games or programs have always done it, it's an important part of making a program or game
 
Lol, I do it because the save is human readable and it's ease of use allows me to work on more important stuff. Worrying about speed over everything isn't always the best way to program. I know for myself, personally, I could definitely write a saving and loading script using buffers, but why would I when the speed difference between saving and loading json versus buffers is absolutely miniscule in real terms in my case, plus my data is already stored in maps/lists, plus I have better things to do than writing code that already exists within the library (or at least, a JSON version of what I am trying to achieve, i.e. saving and loading, already exists).

Worry about speed when speed impacts performance (which I'm sure it does in your case, as you are working with 3D in GM), but until the game is actually negatively impacted by speed concerns, your time is better spent actually making the game (there are more people that never finish a project than there are people who can't optimise for speed, so I think that finishing the project should be priority number 1 over everything else).

On top of everything else, I also like to let people fiddle around with the innards of my game and JSON is a common format that is easy to understand for people who want to mess with a save file.
 

Joe Ellis

Member
You've got a good point, but I think that it's good to start from a good root, I know that with my methods compared to people that use the built in methods that I'm more knowledgeable and understand alot more about what actually happens, you can say that finishing a game is the most important (which it is), but I'm just sharing programming knowledge, which you can't argue is the best, - it actually is, technically,
 
Personally, I get annoyed with people who worry about micro-optimising loops and stuff when they have barely even got the barebones of a game done (not talking about you here, just general folks that post questions). That time is so much better spent getting more of your actual game done, rather than worrying if you can shave 0.5 microseconds off of execution speed by converting every for loop in your game into a repeat loop or whatever. Once you're nearing completion, if there are performance issues, THEN go back and micro-optimise (though, more often than not, it's draw calls that are the problem, not loop structures/save structures/etc). And when you're doing that, the body of knowledge you're talking about will come in super handy, I just think most people don't actually get anywhere near the point where they SHOULD be optimising before they go right ahead and start optimising and slowing down their progress overall.

In any case, with all our competing agendas as question answerers, at least people are getting the full view of programming styles and structures, allowing them to see beyond the veil of "programming" in general.
 
T

Taddio

Guest
@RefresherTowel Totally right. All in all, if the end user doesn't notice anything laggy, he wouldn't care less how the game is made under the hood. That's a developper's illness, not a player's.
I guess it's just satisfying when you have a piece of script, look at it and go "Yep! This works freakin good!!", but that's not what makes the game good/fun by any mean for the player.
 
Top