• 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!

GML (SOLVED) Looping List Causes Runaway Memory?

mako

Member
Hi, guys!

First post here, though I've learned a LOT from the forums since the Gamemaker 7.0 days. (Ah, youth...)

Anyway, I'm working on a small MMO in Gamemaker Studio 2.0 (please, no "network games are crap in Gamemaker" comments, I've read them all and am just having fun with my hobby).

I'm pretty experienced with GML (no D&D since my first try 10+ years ago) but I ran into a really weird issue with a FOR loop and ds_lists in GML 2.0. I already figured out a work around, but I still don't know WHY the workaround works and I'd like to find out. Hopefully someone here will have already seen this and be able to point out which particular stupid mistake I've been making.

So, I'm tracking objects with "collision_rectangle_list" and sorting through the list, comparing it to a previous list, then finding any new objects that weren't on the previous one.

GML:
//in creation event
currentList = ds_list_create();
pastList = ds_list_create();
updateList = ds_list_create();

//in alarm[0], runs every 30 steps
//check for objects in player area


collision_rectangle_list(x-500, y-500, x+500, y+500, otherPlayerObject, true, currentList, false);


for (i=0; i < (ds_list_size(currentList))-1; i++))
    {
        var currentUnit = ds_list_find_value(currentList, i);
        var inList = false;

        for (i=0; i < (ds_list_size(pastList))-1; i  ++))
            {
                if currentUnit == ds_list_find_value(pastList, i)
                    {
                        inList = true;
                        break;
                    }
            }

            If inList == true
                {
                    ds_list_add(updateList, currentUnit);
                }
    }

ds_list_copy(pastList, currentList);
ds_list_clear(currentList);

//do useful stuff with updateList, then...
ds_list_clear(currentList);
Now, this all looked, good, compiled with NO ERRORS, and the server started fine. (I forgot to mention, this is on the server-side, but is isolated from the server/client network matrix, so the issue doesn't come from that end of things.) Once a client connects, this code is called, but after that the code runs with no inputs. It's just the call that is networked.

As soon as this code ran... the game would hang. It wouldn't throw an error. It would just hang. So, out came the trusty debug tool and... RAM usage is skyrocketing. Normal server-side RAM usage with two connected clients is 2.95 MB of RAM, peaking at 3MB of RAM. Once this code is put into the loop, the RAM usage would climb at a rate of 1MB per second, up until something crashed or I closed the game.

Now, my assumption was that I had a memory leak somewhere, but I checked that exhaustively. No leak that I could see.

I wondered if maybe checking the ds_list without putting values in them first might be causing an issue. That shouldn't cause an issue, because an empty list would just return "undefined" (which it did, during testing, several times and ways) or throw an error, but who knows?

Nope, that wasn't the problem. Filling the lists with random values did nothing.

Finally, after several coding sessions, I had this:

GML:
//in creation event
currentList = ds_list_create();
pastList = ds_list_create();
updateList = ds_list_create();

//in alarm[0], runs every 30 steps
//check for objects in player area


collision_rectangle_list(x-500, y-500, x+500, y+500, otherPlayerObject, true, currentList, false);


for (i=0; i < (ds_list_size(currentList)); i++))
    {
        var currentUnit = ds_list_find_value(currentList, i);
        var inList = false;

        for (i=0; i = (ds_list_size(pastList)); i  ++))
            {
                if currentUnit = ds_list_find_value(pastList, i)
                    {
                        inList = true;
                        break;
                    }
            }

            If inList == true
                {
                    ds_list_add(updateList, currentUnit);
                }
    }

ds_list_copy(pastList, currentList);
ds_list_clear(currentList);

//do useful stuff with updateList, then...
ds_list_clear(currentList);
If you didn't catch it... the ONLY difference is that instead of writing the stop value in the FOR loops as "i < ds_list_size(currentList)-1" and "i<ds_list_size(pastList)-1" to account for the difference between "ds_list_size" returning the length (ie, a quantity of 3 contained values) and the ds_list being numbered from 0 (ie, indexes 0, 1, 2), I wrote it them as "i < ds_list_size(currentList)" and "i<ds_list_size(pastList)". That's exactly what you would do if you were using an array, right? Which I've done many times in previous versions of GML, but ds_lists have a couple of useful features that I like using in this context.

Now (if my understanding is correct), my code is now checking for ds_list indexes "0", "1", "2" and an non-existing "3" (which being nonexistent, just returns "undefined") and is perfectly happy with that. The RAM usage stays right at 3MB and never stutters and the game runs as expected. What... the... heck... is... this? Black MAGIC!!! Or maybe I'm just misunderstanding something fundamental here....?

My game is working, none the wiser, but my brain is now lying awake at night, haunted by the thought that my understanding of the GML has been founded on a lie....

Someone help me....

EDIT: This code isn't copy/paste from the IDE, it's handwritten to the forum, so there may be some mistakes in argument #s or such. Please trust me; it wasn't a typo or something simple like that: Studio would have put up a stink in the error reporting if that was the case.
 
Last edited:

FrostyCat

Redemption Seeker
You are using the same looping variable i in both the outer and the inner for loops. They interfere with each other in unpredictable ways and sometimes produce infinite loops. It is a simple typo, and you have to learn that not every mistake manifests in an error messages.
 

mako

Member
Even though I've used the same looping variable "i" in the working code? At this point I'd believe anything, but it seems strange to me that changing the "-1" would fix the issue. I'll try returning the code to my original and using a different loop name when I get home from work!

Yeah, I usually trust the error system to catch outright bugs, but I guess I should taper off there...
 

chamaeleon

Member
Besides the use of the same variable for two different loops, the inner loop uses = instead of < or <=. Even with different variables, the inner loop may not execute as many iterations as expected.
 

TsukaYuriko

☄️
Forum Staff
Moderator
Code that has the effect you expect it to have is not necessarily code that works the way you expect it to do. ;) Syntax checks only catch things you did "obviously wrong" in a way that won't let the code compile. It won't catch things that are syntactically correct but don't do what you intended.

Using the same variable for two different, nested loops is a surefire recipe for disaster, and anything that comes from that and produces a "correct" result is a coincidence.
 

mako

Member
@chamaeleon: yeah, the "=" typo was me on the forum; Gamemaker caught that as expected. I'll edit the post. :)

@TsukaYuriko: when it's put in the "Hey, dummy, result != intent" context, FrostyCat's response makes a lot of sense! :)

Thanks so much, guys. I really should know better (I've had nested loop issues in the past) but the game running fine after I deleted the "-1" I guess it threw me down the wrong track. :) I think I'd better stop coding after midnight....

Anyone have any idea WHY removing the "-1" made it work, though? That's got me puzzled still.
 
Last edited:

mako

Member
Also, can I quote you for my tag?

Code:
Code that has the effect you expect it to have is not necessarily code that works the way you expect it to do.
Speaks a lot to my experience as a hobbyist programmer, but even more so to my professional life.
 

Nidoking

Member
Coincidence is a powerful thing.

You might consider using ds_list_find_index to search the list rather than using the inner for loop. Should be just as many operations, but internal, so your actual code won't be so confusing.
 
Top